adk_doc_audit/
suggestion.rs

1//! Automated fix suggestion engine for documentation audit issues.
2//!
3//! This module provides functionality to generate automated suggestions for fixing
4//! various types of documentation issues including API signature corrections,
5//! version inconsistencies, compilation errors, and diff-style updates.
6
7use crate::{
8    ApiItemType, ApiReference, CompilationError, CrateInfo, ErrorType, PublicApi, Result,
9    VersionReference, VersionType,
10};
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13use tracing::instrument;
14
15/// Engine for generating automated fix suggestions.
16#[derive(Debug)]
17pub struct SuggestionEngine {
18    /// Registry of available crates and their APIs
19    crate_registry: HashMap<String, CrateInfo>,
20    /// Current workspace version information
21    workspace_version: String,
22    /// Cache of generated suggestions to avoid duplicates
23    suggestion_cache: HashMap<String, Vec<Suggestion>>,
24}
25
26/// Represents an automated fix suggestion.
27#[derive(Debug, Clone, PartialEq)]
28pub struct Suggestion {
29    /// Type of suggestion
30    pub suggestion_type: SuggestionType,
31    /// Human-readable description of the suggestion
32    pub description: String,
33    /// Original text that needs to be changed
34    pub original_text: String,
35    /// Suggested replacement text
36    pub suggested_text: String,
37    /// File path where the change should be made
38    pub file_path: PathBuf,
39    /// Line number where the change should be made (if known)
40    pub line_number: Option<usize>,
41    /// Column number where the change should be made (if known)
42    pub column_number: Option<usize>,
43    /// Confidence level of the suggestion (0.0 to 1.0)
44    pub confidence: f64,
45    /// Additional context or explanation
46    pub context: Option<String>,
47    /// Diff-style representation of the change
48    pub diff: Option<String>,
49}
50
51/// Types of automated suggestions that can be generated.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum SuggestionType {
54    /// API signature correction
55    ApiSignatureCorrection,
56    /// Version number update
57    VersionUpdate,
58    /// Compilation fix
59    CompilationFix,
60    /// Import statement fix
61    ImportFix,
62    /// Dependency addition
63    DependencyAddition,
64    /// Deprecated API replacement
65    DeprecatedApiReplacement,
66    /// Async pattern fix
67    AsyncPatternFix,
68    /// Link correction
69    LinkCorrection,
70    /// Feature flag correction
71    FeatureFlagCorrection,
72    /// Documentation structure improvement
73    StructureImprovement,
74}
75
76/// Configuration for suggestion generation.
77#[derive(Debug, Clone)]
78pub struct SuggestionConfig {
79    /// Minimum confidence threshold for suggestions
80    pub min_confidence: f64,
81    /// Maximum number of suggestions per issue
82    pub max_suggestions_per_issue: usize,
83    /// Whether to generate diff-style output
84    pub generate_diffs: bool,
85    /// Whether to include context in suggestions
86    pub include_context: bool,
87    /// Whether to cache suggestions
88    pub enable_caching: bool,
89}
90
91impl SuggestionEngine {
92    /// Creates a new suggestion engine.
93    ///
94    /// # Arguments
95    ///
96    /// * `crate_registry` - Registry of available crates and their APIs
97    /// * `workspace_version` - Current workspace version
98    ///
99    /// # Returns
100    ///
101    /// A new `SuggestionEngine` instance.
102    pub fn new(crate_registry: HashMap<String, CrateInfo>, workspace_version: String) -> Self {
103        Self { crate_registry, workspace_version, suggestion_cache: HashMap::new() }
104    }
105
106    /// Creates a new suggestion engine with empty registry (for orchestrator use).
107    pub fn new_empty() -> Self {
108        Self {
109            crate_registry: HashMap::new(),
110            workspace_version: "0.1.0".to_string(),
111            suggestion_cache: HashMap::new(),
112        }
113    }
114
115    /// Generates API signature correction suggestions.
116    ///
117    /// # Arguments
118    ///
119    /// * `api_ref` - The API reference that needs correction
120    /// * `file_path` - Path to the file containing the reference
121    /// * `config` - Configuration for suggestion generation
122    ///
123    /// # Returns
124    ///
125    /// A vector of suggestions for correcting the API signature.
126    #[instrument(skip(self, config))]
127    pub fn suggest_api_signature_corrections(
128        &self,
129        api_ref: &ApiReference,
130        file_path: &Path,
131        config: &SuggestionConfig,
132    ) -> Result<Vec<Suggestion>> {
133        let mut suggestions = Vec::new();
134
135        // Check cache first
136        let cache_key = format!("api_{}_{}", api_ref.crate_name, api_ref.item_path);
137        if config.enable_caching {
138            if let Some(cached) = self.suggestion_cache.get(&cache_key) {
139                return Ok(cached.clone());
140            }
141        }
142
143        // Find the crate in our registry
144        if let Some(crate_info) = self.crate_registry.get(&api_ref.crate_name) {
145            // Look for exact matches first
146            if let Some(exact_match) = self.find_exact_api_match(crate_info, api_ref) {
147                let suggestion = self.create_api_correction_suggestion(
148                    api_ref,
149                    exact_match,
150                    file_path,
151                    1.0, // High confidence for exact matches
152                    config,
153                )?;
154                suggestions.push(suggestion);
155            } else {
156                // Look for similar APIs (fuzzy matching)
157                let similar_apis = self.find_similar_apis(crate_info, api_ref);
158                for (api, confidence) in similar_apis {
159                    if confidence >= config.min_confidence {
160                        let suggestion = self.create_api_correction_suggestion(
161                            api_ref, api, file_path, confidence, config,
162                        )?;
163                        suggestions.push(suggestion);
164                    }
165                }
166            }
167
168            // Check for deprecated APIs and suggest replacements
169            if let Some(deprecated_replacement) =
170                self.find_deprecated_replacement(crate_info, api_ref)
171            {
172                let suggestion =
173                    Suggestion {
174                        suggestion_type: SuggestionType::DeprecatedApiReplacement,
175                        description: format!(
176                            "Replace deprecated API '{}' with '{}'",
177                            api_ref.item_path, deprecated_replacement.path
178                        ),
179                        original_text: api_ref.item_path.clone(),
180                        suggested_text: deprecated_replacement.path.clone(),
181                        file_path: file_path.to_path_buf(),
182                        line_number: Some(api_ref.line_number),
183                        column_number: None,
184                        confidence: 0.9,
185                        context: Some(format!(
186                            "The API '{}' has been deprecated. Use '{}' instead.",
187                            api_ref.item_path, deprecated_replacement.path
188                        )),
189                        diff: if config.generate_diffs {
190                            Some(self.generate_simple_diff(
191                                &api_ref.item_path,
192                                &deprecated_replacement.path,
193                            ))
194                        } else {
195                            None
196                        },
197                    };
198                suggestions.push(suggestion);
199            }
200        } else {
201            // Crate not found - suggest adding dependency
202            let suggestion = Suggestion {
203                suggestion_type: SuggestionType::DependencyAddition,
204                description: format!("Add missing dependency '{}'", api_ref.crate_name),
205                original_text: String::new(),
206                suggested_text: format!("{} = \"{}\"", api_ref.crate_name, self.workspace_version),
207                file_path: file_path.to_path_buf(),
208                line_number: None,
209                column_number: None,
210                confidence: 0.8,
211                context: Some(format!(
212                    "The crate '{}' is not found in dependencies. Add it to Cargo.toml.",
213                    api_ref.crate_name
214                )),
215                diff: None,
216            };
217            suggestions.push(suggestion);
218        }
219
220        // Limit suggestions per configuration
221        suggestions.truncate(config.max_suggestions_per_issue);
222
223        Ok(suggestions)
224    }
225
226    /// Generates version consistency correction suggestions.
227    ///
228    /// # Arguments
229    ///
230    /// * `version_ref` - The version reference that needs correction
231    /// * `crate_name` - Name of the crate for this version reference
232    /// * `file_path` - Path to the file containing the reference
233    /// * `config` - Configuration for suggestion generation
234    ///
235    /// # Returns
236    ///
237    /// A vector of suggestions for correcting version inconsistencies.
238    #[instrument(skip(self, config))]
239    pub fn suggest_version_corrections(
240        &self,
241        version_ref: &VersionReference,
242        crate_name: &str,
243        file_path: &Path,
244        config: &SuggestionConfig,
245    ) -> Result<Vec<Suggestion>> {
246        let mut suggestions = Vec::new();
247
248        let correct_version = match version_ref.version_type {
249            VersionType::CrateVersion => {
250                // Get the correct version from crate registry
251                if let Some(crate_info) = self.crate_registry.get(crate_name) {
252                    crate_info.version.clone()
253                } else {
254                    self.workspace_version.clone()
255                }
256            }
257            VersionType::RustVersion => {
258                // Get Rust version from workspace
259                self.get_workspace_rust_version().unwrap_or_else(|| "1.85.0".to_string())
260            }
261            VersionType::WorkspaceVersion => {
262                // Use workspace version
263                self.workspace_version.clone()
264            }
265            VersionType::Generic => {
266                // Get dependency version from workspace
267                self.get_dependency_version(crate_name)
268                    .unwrap_or_else(|| self.workspace_version.clone())
269            }
270        };
271
272        if version_ref.version != correct_version {
273            let suggestion = Suggestion {
274                suggestion_type: SuggestionType::VersionUpdate,
275                description: format!(
276                    "Update {} version from '{}' to '{}'",
277                    crate_name, version_ref.version, correct_version
278                ),
279                original_text: version_ref.version.clone(),
280                suggested_text: correct_version.clone(),
281                file_path: file_path.to_path_buf(),
282                line_number: Some(version_ref.line_number),
283                column_number: None,
284                confidence: 0.95,
285                context: if config.include_context {
286                    Some(format!(
287                        "Version '{}' is outdated. Current version is '{}'.",
288                        version_ref.version, correct_version
289                    ))
290                } else {
291                    None
292                },
293                diff: if config.generate_diffs {
294                    Some(self.generate_simple_diff(&version_ref.version, &correct_version))
295                } else {
296                    None
297                },
298            };
299            suggestions.push(suggestion);
300        }
301
302        Ok(suggestions)
303    }
304
305    /// Generates compilation fix suggestions based on compilation errors.
306    ///
307    /// # Arguments
308    ///
309    /// * `errors` - Compilation errors to generate fixes for
310    /// * `file_path` - Path to the file with compilation errors
311    /// * `config` - Configuration for suggestion generation
312    ///
313    /// # Returns
314    ///
315    /// A vector of suggestions for fixing compilation errors.
316    #[instrument(skip(self, config))]
317    pub fn suggest_compilation_fixes(
318        &self,
319        errors: &[CompilationError],
320        file_path: &Path,
321        config: &SuggestionConfig,
322    ) -> Result<Vec<Suggestion>> {
323        let mut suggestions = Vec::new();
324
325        for error in errors {
326            match error.error_type {
327                ErrorType::UnresolvedImport => {
328                    if let Some(import_suggestion) = self.suggest_import_fix(&error.message) {
329                        let suggestion = Suggestion {
330                            suggestion_type: SuggestionType::ImportFix,
331                            description: format!("Add missing import: {}", import_suggestion),
332                            original_text: String::new(),
333                            suggested_text: import_suggestion.clone(),
334                            file_path: file_path.to_path_buf(),
335                            line_number: error.line,
336                            column_number: error.column,
337                            confidence: 0.8,
338                            context: if config.include_context {
339                                Some(format!(
340                                    "Import '{}' to resolve the unresolved reference.",
341                                    import_suggestion
342                                ))
343                            } else {
344                                None
345                            },
346                            diff: None,
347                        };
348                        suggestions.push(suggestion);
349                    }
350                }
351                ErrorType::MissingDependency => {
352                    if let Some(dep_suggestion) = self.suggest_dependency_addition(&error.message) {
353                        let suggestion = Suggestion {
354                            suggestion_type: SuggestionType::DependencyAddition,
355                            description: format!("Add missing dependency: {}", dep_suggestion),
356                            original_text: String::new(),
357                            suggested_text: dep_suggestion.clone(),
358                            file_path: file_path.to_path_buf(),
359                            line_number: None,
360                            column_number: None,
361                            confidence: 0.85,
362                            context: if config.include_context {
363                                Some("Add this dependency to your Cargo.toml file.".to_string())
364                            } else {
365                                None
366                            },
367                            diff: None,
368                        };
369                        suggestions.push(suggestion);
370                    }
371                }
372                ErrorType::AsyncPatternError => {
373                    let async_suggestions = self.suggest_async_pattern_fixes(&error.message);
374                    for async_fix in async_suggestions {
375                        let suggestion = Suggestion {
376                            suggestion_type: SuggestionType::AsyncPatternFix,
377                            description: format!("Fix async pattern: {}", async_fix),
378                            original_text: String::new(),
379                            suggested_text: async_fix.clone(),
380                            file_path: file_path.to_path_buf(),
381                            line_number: error.line,
382                            column_number: error.column,
383                            confidence: 0.75,
384                            context: if config.include_context {
385                                Some(
386                                    "Async code requires proper runtime setup and await usage."
387                                        .to_string(),
388                                )
389                            } else {
390                                None
391                            },
392                            diff: None,
393                        };
394                        suggestions.push(suggestion);
395                    }
396                }
397                ErrorType::DeprecatedApi => {
398                    if let Some(replacement) =
399                        self.suggest_deprecated_api_replacement(&error.message)
400                    {
401                        let suggestion = Suggestion {
402                            suggestion_type: SuggestionType::DeprecatedApiReplacement,
403                            description: format!("Replace deprecated API with: {}", replacement),
404                            original_text: String::new(),
405                            suggested_text: replacement.clone(),
406                            file_path: file_path.to_path_buf(),
407                            line_number: error.line,
408                            column_number: error.column,
409                            confidence: 0.9,
410                            context: if config.include_context {
411                                Some(
412                                    "This API has been deprecated. Use the suggested replacement."
413                                        .to_string(),
414                                )
415                            } else {
416                                None
417                            },
418                            diff: None,
419                        };
420                        suggestions.push(suggestion);
421                    }
422                }
423                _ => {
424                    // Generic compilation fix suggestions
425                    if let Some(generic_fix) = self.suggest_generic_compilation_fix(&error.message)
426                    {
427                        let suggestion = Suggestion {
428                            suggestion_type: SuggestionType::CompilationFix,
429                            description: format!("Compilation fix: {}", generic_fix),
430                            original_text: String::new(),
431                            suggested_text: generic_fix.clone(),
432                            file_path: file_path.to_path_buf(),
433                            line_number: error.line,
434                            column_number: error.column,
435                            confidence: 0.6,
436                            context: if config.include_context {
437                                Some("General compilation fix suggestion.".to_string())
438                            } else {
439                                None
440                            },
441                            diff: None,
442                        };
443                        suggestions.push(suggestion);
444                    }
445                }
446            }
447        }
448
449        // Limit suggestions per configuration
450        suggestions.truncate(config.max_suggestions_per_issue);
451
452        Ok(suggestions)
453    }
454
455    /// Generates diff-style update suggestions.
456    ///
457    /// # Arguments
458    ///
459    /// * `original_content` - Original file content
460    /// * `suggestions` - List of suggestions to apply
461    /// * `file_path` - Path to the file
462    ///
463    /// # Returns
464    ///
465    /// A diff-style string showing the proposed changes.
466    pub fn generate_diff_suggestions(
467        &self,
468        original_content: &str,
469        suggestions: &[Suggestion],
470        file_path: &Path,
471    ) -> Result<String> {
472        let mut diff_output = String::new();
473
474        diff_output.push_str(&format!("--- {}\n", file_path.display()));
475        diff_output.push_str(&format!("+++ {}\n", file_path.display()));
476
477        let lines: Vec<&str> = original_content.lines().collect();
478        let mut modified_lines = lines.clone();
479
480        // Apply suggestions to create modified content
481        for suggestion in suggestions {
482            if let Some(line_num) = suggestion.line_number {
483                if line_num > 0 && line_num <= modified_lines.len() {
484                    let line_index = line_num - 1;
485                    let original_line = modified_lines[line_index];
486                    let modified_line = original_line
487                        .replace(&suggestion.original_text, &suggestion.suggested_text);
488                    modified_lines[line_index] = Box::leak(modified_line.into_boxed_str());
489                }
490            }
491        }
492
493        // Generate unified diff format
494        for (i, (original, modified)) in lines.iter().zip(modified_lines.iter()).enumerate() {
495            if original != modified {
496                diff_output.push_str(&format!("@@ -{},{} +{},{} @@\n", i + 1, 1, i + 1, 1));
497                diff_output.push_str(&format!("-{}\n", original));
498                diff_output.push_str(&format!("+{}\n", modified));
499            }
500        }
501
502        Ok(diff_output)
503    }
504
505    // Private helper methods
506
507    /// Finds an exact API match in the crate registry.
508    fn find_exact_api_match<'a>(
509        &self,
510        crate_info: &'a CrateInfo,
511        api_ref: &ApiReference,
512    ) -> Option<&'a PublicApi> {
513        crate_info.public_apis.iter().find(|api| {
514            api.path == api_ref.item_path
515                && self.api_types_match(&api.item_type, &api_ref.item_type)
516        })
517    }
518
519    /// Finds similar APIs using fuzzy matching.
520    fn find_similar_apis<'a>(
521        &self,
522        crate_info: &'a CrateInfo,
523        api_ref: &ApiReference,
524    ) -> Vec<(&'a PublicApi, f64)> {
525        let mut similar_apis = Vec::new();
526
527        for api in &crate_info.public_apis {
528            let similarity = self.calculate_similarity(&api_ref.item_path, &api.path);
529            if similarity > 0.6 {
530                // Threshold for similarity
531                similar_apis.push((api, similarity));
532            }
533        }
534
535        // Sort by similarity (highest first)
536        similar_apis.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
537        similar_apis
538    }
539
540    /// Finds replacement for deprecated API.
541    fn find_deprecated_replacement<'a>(
542        &self,
543        crate_info: &'a CrateInfo,
544        api_ref: &ApiReference,
545    ) -> Option<&'a PublicApi> {
546        // Look for non-deprecated APIs with similar names
547        crate_info.public_apis.iter().find(|api| {
548            !api.deprecated && self.calculate_similarity(&api_ref.item_path, &api.path) > 0.8
549        })
550    }
551
552    /// Creates an API correction suggestion.
553    fn create_api_correction_suggestion(
554        &self,
555        api_ref: &ApiReference,
556        correct_api: &PublicApi,
557        file_path: &Path,
558        confidence: f64,
559        config: &SuggestionConfig,
560    ) -> Result<Suggestion> {
561        Ok(Suggestion {
562            suggestion_type: SuggestionType::ApiSignatureCorrection,
563            description: format!(
564                "Correct API signature from '{}' to '{}'",
565                api_ref.item_path, correct_api.path
566            ),
567            original_text: api_ref.item_path.clone(),
568            suggested_text: correct_api.path.clone(),
569            file_path: file_path.to_path_buf(),
570            line_number: Some(api_ref.line_number),
571            column_number: None,
572            confidence,
573            context: if config.include_context {
574                Some(format!("Current signature: {}", correct_api.signature))
575            } else {
576                None
577            },
578            diff: if config.generate_diffs {
579                Some(self.generate_simple_diff(&api_ref.item_path, &correct_api.path))
580            } else {
581                None
582            },
583        })
584    }
585
586    /// Checks if API types match (with some flexibility).
587    fn api_types_match(&self, type1: &ApiItemType, type2: &ApiItemType) -> bool {
588        type1 == type2
589    }
590
591    /// Calculates similarity between two strings using Levenshtein distance.
592    fn calculate_similarity(&self, s1: &str, s2: &str) -> f64 {
593        let len1 = s1.len();
594        let len2 = s2.len();
595
596        if len1 == 0 && len2 == 0 {
597            return 1.0;
598        }
599
600        if len1 == 0 || len2 == 0 {
601            return 0.0;
602        }
603
604        let distance = self.levenshtein_distance(s1, s2);
605        let max_len = len1.max(len2);
606
607        1.0 - (distance as f64 / max_len as f64)
608    }
609
610    /// Calculates Levenshtein distance between two strings.
611    fn levenshtein_distance(&self, s1: &str, s2: &str) -> usize {
612        let chars1: Vec<char> = s1.chars().collect();
613        let chars2: Vec<char> = s2.chars().collect();
614        let len1 = chars1.len();
615        let len2 = chars2.len();
616
617        let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
618
619        for (i, row) in matrix.iter_mut().enumerate().take(len1 + 1) {
620            row[0] = i;
621        }
622        #[allow(clippy::needless_range_loop)]
623        for j in 0..=len2 {
624            matrix[0][j] = j;
625        }
626
627        for i in 1..=len1 {
628            for j in 1..=len2 {
629                let cost = if chars1[i - 1] == chars2[j - 1] { 0 } else { 1 };
630                matrix[i][j] = (matrix[i - 1][j] + 1)
631                    .min(matrix[i][j - 1] + 1)
632                    .min(matrix[i - 1][j - 1] + cost);
633            }
634        }
635
636        matrix[len1][len2]
637    }
638
639    /// Gets the workspace Rust version.
640    fn get_workspace_rust_version(&self) -> Option<String> {
641        // This would typically read from workspace Cargo.toml
642        // For now, return a default
643        Some("1.85.0".to_string())
644    }
645
646    /// Gets the version of a specific dependency.
647    fn get_dependency_version(&self, crate_name: &str) -> Option<String> {
648        self.crate_registry.get(crate_name).map(|info| info.version.clone())
649    }
650
651    /// Suggests import fixes based on error message.
652    fn suggest_import_fix(&self, error_message: &str) -> Option<String> {
653        if error_message.contains("adk_core") {
654            Some("use adk_core::*;".to_string())
655        } else if error_message.contains("adk_model") {
656            Some("use adk_model::*;".to_string())
657        } else if error_message.contains("adk_agent") {
658            Some("use adk_agent::*;".to_string())
659        } else if error_message.contains("tokio") {
660            Some("use tokio;".to_string())
661        } else if error_message.contains("serde") {
662            Some("use serde::{Serialize, Deserialize};".to_string())
663        } else if error_message.contains("anyhow") {
664            Some("use anyhow::Result;".to_string())
665        } else {
666            None
667        }
668    }
669
670    /// Suggests dependency additions based on error message.
671    fn suggest_dependency_addition(&self, error_message: &str) -> Option<String> {
672        if error_message.contains("adk_core") {
673            Some("adk-core = { path = \"../adk-core\" }".to_string())
674        } else if error_message.contains("adk_model") {
675            Some("adk-model = { path = \"../adk-model\" }".to_string())
676        } else if error_message.contains("tokio") {
677            Some("tokio = { version = \"1.0\", features = [\"full\"] }".to_string())
678        } else if error_message.contains("serde") {
679            Some("serde = { version = \"1.0\", features = [\"derive\"] }".to_string())
680        } else if error_message.contains("anyhow") {
681            Some("anyhow = \"1.0\"".to_string())
682        } else {
683            None
684        }
685    }
686
687    /// Suggests async pattern fixes.
688    fn suggest_async_pattern_fixes(&self, error_message: &str) -> Vec<String> {
689        let mut suggestions = Vec::new();
690
691        if error_message.contains("async fn main") {
692            suggestions.push("#[tokio::main]".to_string());
693        }
694        if error_message.contains("await") {
695            suggestions.push("Add .await to async function calls".to_string());
696        }
697        if error_message.contains("runtime") {
698            suggestions
699                .push("Set up tokio runtime with #[tokio::main] or Runtime::new()".to_string());
700        }
701
702        suggestions
703    }
704
705    /// Suggests deprecated API replacements.
706    fn suggest_deprecated_api_replacement(&self, _error_message: &str) -> Option<String> {
707        // This would typically use a mapping of deprecated APIs to their replacements
708        // For now, provide generic advice
709        Some("Check the latest documentation for the current API".to_string())
710    }
711
712    /// Suggests generic compilation fixes.
713    fn suggest_generic_compilation_fix(&self, error_message: &str) -> Option<String> {
714        if error_message.contains("cannot find") {
715            Some("Check imports and ensure the module is available".to_string())
716        } else if error_message.contains("mismatched types") {
717            Some("Check type annotations and ensure types match".to_string())
718        } else if error_message.contains("borrow") {
719            Some("Check borrowing rules and lifetime annotations".to_string())
720        } else {
721            None
722        }
723    }
724
725    /// Generates documentation placement suggestions for new features.
726    ///
727    /// # Arguments
728    ///
729    /// * `undocumented_apis` - APIs that are not documented
730    /// * `workspace_path` - Path to the workspace root
731    /// * `docs_path` - Path to the documentation directory
732    /// * `config` - Configuration for suggestion generation
733    ///
734    /// # Returns
735    ///
736    /// A vector of suggestions for where to document new features.
737    #[instrument(skip(self, config))]
738    pub fn suggest_documentation_placement(
739        &self,
740        undocumented_apis: &[PublicApi],
741        workspace_path: &Path,
742        docs_path: &Path,
743        config: &SuggestionConfig,
744    ) -> Result<Vec<Suggestion>> {
745        let mut suggestions = Vec::new();
746
747        for api in undocumented_apis {
748            // Determine the best documentation file for this API
749            let suggested_file = self.determine_documentation_file(api, docs_path)?;
750            let suggested_section = self.determine_documentation_section(api);
751
752            let suggestion = Suggestion {
753                suggestion_type: SuggestionType::StructureImprovement,
754                description: format!("Document '{}' in {}", api.path, suggested_file.display()),
755                original_text: String::new(),
756                suggested_text: self.generate_documentation_template(api),
757                file_path: suggested_file,
758                line_number: None,
759                column_number: None,
760                confidence: 0.8,
761                context: if config.include_context {
762                    Some(format!(
763                        "Add documentation for {} in the {} section",
764                        api.path, suggested_section
765                    ))
766                } else {
767                    None
768                },
769                diff: None,
770            };
771            suggestions.push(suggestion);
772        }
773
774        // Suggest documentation structure improvements
775        let structure_suggestions = self.suggest_structure_improvements(docs_path, config)?;
776        suggestions.extend(structure_suggestions);
777
778        // Limit suggestions per configuration
779        suggestions.truncate(config.max_suggestions_per_issue);
780
781        Ok(suggestions)
782    }
783
784    /// Suggests improvements to documentation structure.
785    ///
786    /// # Arguments
787    ///
788    /// * `docs_path` - Path to the documentation directory
789    /// * `config` - Configuration for suggestion generation
790    ///
791    /// # Returns
792    ///
793    /// A vector of suggestions for improving documentation structure.
794    #[instrument(skip(self, config))]
795    pub fn suggest_structure_improvements(
796        &self,
797        docs_path: &Path,
798        config: &SuggestionConfig,
799    ) -> Result<Vec<Suggestion>> {
800        let mut suggestions = Vec::new();
801
802        // Check for missing essential documentation files
803        let essential_files = [
804            ("getting-started.md", "Getting Started Guide"),
805            ("api-reference.md", "API Reference"),
806            ("examples.md", "Examples and Tutorials"),
807            ("migration-guide.md", "Migration Guide"),
808            ("troubleshooting.md", "Troubleshooting Guide"),
809            ("changelog.md", "Changelog"),
810        ];
811
812        for (filename, description) in &essential_files {
813            let file_path = docs_path.join(filename);
814            if !file_path.exists() {
815                let suggestion = Suggestion {
816                    suggestion_type: SuggestionType::StructureImprovement,
817                    description: format!("Create missing {}", description),
818                    original_text: String::new(),
819                    suggested_text: self.generate_file_template(filename),
820                    file_path,
821                    line_number: None,
822                    column_number: None,
823                    confidence: 0.9,
824                    context: if config.include_context {
825                        Some(format!(
826                            "{} is essential for comprehensive documentation",
827                            description
828                        ))
829                    } else {
830                        None
831                    },
832                    diff: None,
833                };
834                suggestions.push(suggestion);
835            }
836        }
837
838        // Check for proper index/navigation structure
839        let index_path = docs_path.join("index.md");
840        if !index_path.exists() {
841            let suggestion = Suggestion {
842                suggestion_type: SuggestionType::StructureImprovement,
843                description: "Create documentation index file".to_string(),
844                original_text: String::new(),
845                suggested_text: self.generate_index_template(docs_path)?,
846                file_path: index_path,
847                line_number: None,
848                column_number: None,
849                confidence: 0.95,
850                context: if config.include_context {
851                    Some("An index file helps users navigate the documentation".to_string())
852                } else {
853                    None
854                },
855                diff: None,
856            };
857            suggestions.push(suggestion);
858        }
859
860        // Suggest organizing documentation by feature/crate
861        let crate_organization_suggestions =
862            self.suggest_crate_based_organization(docs_path, config)?;
863        suggestions.extend(crate_organization_suggestions);
864
865        Ok(suggestions)
866    }
867
868    /// Suggests organizing documentation by crate/feature.
869    fn suggest_crate_based_organization(
870        &self,
871        docs_path: &Path,
872        config: &SuggestionConfig,
873    ) -> Result<Vec<Suggestion>> {
874        let mut suggestions = Vec::new();
875
876        // Check if crate-specific directories exist
877        for (crate_name, crate_info) in &self.crate_registry {
878            let crate_docs_dir = docs_path.join(crate_name);
879            if !crate_docs_dir.exists() && !crate_info.public_apis.is_empty() {
880                let suggestion = Suggestion {
881                    suggestion_type: SuggestionType::StructureImprovement,
882                    description: format!("Create documentation directory for {}", crate_name),
883                    original_text: String::new(),
884                    suggested_text: format!("Create directory: {}/", crate_docs_dir.display()),
885                    file_path: crate_docs_dir.clone(),
886                    line_number: None,
887                    column_number: None,
888                    confidence: 0.85,
889                    context: if config.include_context {
890                        Some("Organize documentation by crate for better structure".to_string())
891                    } else {
892                        None
893                    },
894                    diff: None,
895                };
896                suggestions.push(suggestion);
897
898                // Suggest creating crate-specific files
899                let crate_files = [
900                    ("README.md", format!("{} Overview", crate_name)),
901                    ("api.md", format!("{} API Reference", crate_name)),
902                    ("examples.md", format!("{} Examples", crate_name)),
903                ];
904
905                for (filename, description) in &crate_files {
906                    let file_path = crate_docs_dir.join(filename);
907                    let suggestion = Suggestion {
908                        suggestion_type: SuggestionType::StructureImprovement,
909                        description: format!("Create {}", description),
910                        original_text: String::new(),
911                        suggested_text: self
912                            .generate_crate_file_template(crate_name, filename, crate_info),
913                        file_path,
914                        line_number: None,
915                        column_number: None,
916                        confidence: 0.8,
917                        context: if config.include_context {
918                            Some(format!("Dedicated {} documentation", description))
919                        } else {
920                            None
921                        },
922                        diff: None,
923                    };
924                    suggestions.push(suggestion);
925                }
926            }
927        }
928
929        Ok(suggestions)
930    }
931
932    // Private helper methods for documentation placement
933
934    /// Determines the best documentation file for an API.
935    fn determine_documentation_file(&self, api: &PublicApi, docs_path: &Path) -> Result<PathBuf> {
936        // Extract crate name from API path
937        let crate_name = self.extract_crate_name_from_api(&api.path);
938
939        // Check if crate-specific documentation exists
940        let crate_docs_dir = docs_path.join(&crate_name);
941        if crate_docs_dir.exists() {
942            match api.item_type {
943                ApiItemType::Trait => Ok(crate_docs_dir.join("traits.md")),
944                ApiItemType::Struct => Ok(crate_docs_dir.join("structs.md")),
945                ApiItemType::Function => Ok(crate_docs_dir.join("functions.md")),
946                ApiItemType::Enum => Ok(crate_docs_dir.join("enums.md")),
947                ApiItemType::Constant => Ok(crate_docs_dir.join("constants.md")),
948                ApiItemType::Method => Ok(crate_docs_dir.join("methods.md")),
949                ApiItemType::Module => Ok(crate_docs_dir.join("modules.md")),
950                ApiItemType::TypeAlias => Ok(crate_docs_dir.join("types.md")),
951                ApiItemType::Unknown => Ok(crate_docs_dir.join("misc.md")),
952            }
953        } else {
954            // Fall back to general API reference
955            Ok(docs_path.join("api-reference.md"))
956        }
957    }
958
959    /// Determines the appropriate documentation section for an API.
960    fn determine_documentation_section(&self, api: &PublicApi) -> String {
961        match api.item_type {
962            ApiItemType::Trait => "Traits".to_string(),
963            ApiItemType::Struct => "Structs".to_string(),
964            ApiItemType::Function => "Functions".to_string(),
965            ApiItemType::Enum => "Enums".to_string(),
966            ApiItemType::Constant => "Constants".to_string(),
967            ApiItemType::Method => "Methods".to_string(),
968            ApiItemType::Module => "Modules".to_string(),
969            ApiItemType::TypeAlias => "Type Aliases".to_string(),
970            ApiItemType::Unknown => "Miscellaneous".to_string(),
971        }
972    }
973
974    /// Generates a documentation template for an API.
975    fn generate_documentation_template(&self, api: &PublicApi) -> String {
976        let section = self.determine_documentation_section(api);
977
978        format!(
979            r#"## {}
980
981### `{}`
982
983{}
984
985#### Signature
986
987```rust
988{}
989```
990
991#### Description
992
993[Add description here]
994
995#### Examples
996
997```rust
998// Add example usage here
999```
1000
1001#### See Also
1002
1003- [Related documentation]
1004
1005"#,
1006            section,
1007            api.path,
1008            api.documentation.as_deref().unwrap_or("[Add documentation here]"),
1009            api.signature
1010        )
1011    }
1012
1013    /// Generates a template for a documentation file.
1014    fn generate_file_template(&self, filename: &str) -> String {
1015        match filename {
1016            "getting-started.md" => r#"# Getting Started
1017
1018## Installation
1019
1020Add this to your `Cargo.toml`:
1021
1022```toml
1023[dependencies]
1024adk-rust = "0.1.0"
1025```
1026
1027## Quick Start
1028
1029[Add quick start guide here]
1030
1031## Next Steps
1032
1033- [API Reference](api-reference.md)
1034- [Examples](examples.md)
1035"#
1036            .to_string(),
1037            "api-reference.md" => r#"# API Reference
1038
1039## Overview
1040
1041This document provides a comprehensive reference for all public APIs.
1042
1043## Modules
1044
1045[List modules here]
1046
1047## Traits
1048
1049[List traits here]
1050
1051## Structs
1052
1053[List structs here]
1054
1055## Functions
1056
1057[List functions here]
1058"#
1059            .to_string(),
1060            "examples.md" => r#"# Examples and Tutorials
1061
1062## Basic Examples
1063
1064### Hello World
1065
1066```rust
1067// Add basic example here
1068```
1069
1070## Advanced Examples
1071
1072### Complex Usage
1073
1074```rust
1075// Add advanced example here
1076```
1077
1078## Tutorials
1079
1080- [Tutorial 1](tutorials/tutorial-1.md)
1081- [Tutorial 2](tutorials/tutorial-2.md)
1082"#
1083            .to_string(),
1084            "migration-guide.md" => r#"# Migration Guide
1085
1086## Migrating from Previous Versions
1087
1088### Version 0.0.x to 0.1.x
1089
1090[Add migration instructions here]
1091
1092## Breaking Changes
1093
1094[List breaking changes here]
1095
1096## Deprecated APIs
1097
1098[List deprecated APIs and their replacements here]
1099"#
1100            .to_string(),
1101            "troubleshooting.md" => r#"# Troubleshooting
1102
1103## Common Issues
1104
1105### Issue 1
1106
1107**Problem:** [Describe problem]
1108
1109**Solution:** [Describe solution]
1110
1111### Issue 2
1112
1113**Problem:** [Describe problem]
1114
1115**Solution:** [Describe solution]
1116
1117## Getting Help
1118
1119- [GitHub Issues](https://github.com/zavora-ai/adk-rust/issues)
1120- [Discussions](https://github.com/zavora-ai/adk-rust/discussions)
1121"#
1122            .to_string(),
1123            "changelog.md" => r#"# Changelog
1124
1125All notable changes to this project will be documented in this file.
1126
1127The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1128and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1129
1130## [Unreleased]
1131
1132### Added
1133- [New features]
1134
1135### Changed
1136- [Changes in existing functionality]
1137
1138### Deprecated
1139- [Soon-to-be removed features]
1140
1141### Removed
1142- [Removed features]
1143
1144### Fixed
1145- [Bug fixes]
1146
1147### Security
1148- [Security improvements]
1149"#
1150            .to_string(),
1151            _ => format!(
1152                "# {}\n\n[Add content here]\n",
1153                filename.replace(".md", "").replace("-", " ").to_uppercase()
1154            ),
1155        }
1156    }
1157
1158    /// Generates an index template for the documentation.
1159    fn generate_index_template(&self, docs_path: &Path) -> Result<String> {
1160        let mut index_content = String::from(
1161            r#"# ADK-Rust Documentation
1162
1163Welcome to the ADK-Rust documentation!
1164
1165## Getting Started
1166
1167- [Installation and Setup](getting-started.md)
1168- [Quick Start Guide](getting-started.md#quick-start)
1169
1170## Core Documentation
1171
1172- [API Reference](api-reference.md)
1173- [Examples and Tutorials](examples.md)
1174
1175## Crates
1176
1177"#,
1178        );
1179
1180        // Add crate-specific documentation links
1181        for crate_name in self.crate_registry.keys() {
1182            let crate_docs_dir = docs_path.join(crate_name);
1183            if crate_docs_dir.exists() {
1184                index_content.push_str(&format!("- [{}]({})\n", crate_name, crate_name));
1185            } else {
1186                // Include crate even if directory doesn't exist yet
1187                index_content.push_str(&format!("- [{}]({}/README.md)\n", crate_name, crate_name));
1188            }
1189        }
1190
1191        index_content.push_str(
1192            r#"
1193## Additional Resources
1194
1195- [Migration Guide](migration-guide.md)
1196- [Troubleshooting](troubleshooting.md)
1197- [Changelog](changelog.md)
1198
1199## Contributing
1200
1201- [Contributing Guidelines](../CONTRIBUTING.md)
1202- [Development Setup](development.md)
1203"#,
1204        );
1205
1206        Ok(index_content)
1207    }
1208
1209    /// Generates a template for crate-specific documentation files.
1210    fn generate_crate_file_template(
1211        &self,
1212        crate_name: &str,
1213        filename: &str,
1214        crate_info: &CrateInfo,
1215    ) -> String {
1216        match filename {
1217            "README.md" => {
1218                format!(
1219                    r#"# {}
1220
1221## Overview
1222
1223[Add crate overview here]
1224
1225## Installation
1226
1227```toml
1228[dependencies]
1229{} = "{}"
1230```
1231
1232## Features
1233
1234{}
1235
1236## Quick Start
1237
1238```rust
1239use {}::*;
1240
1241// Add quick start example here
1242```
1243
1244## API Reference
1245
1246- [Traits](traits.md)
1247- [Structs](structs.md)
1248- [Functions](functions.md)
1249
1250## Examples
1251
1252See [examples.md](examples.md) for detailed usage examples.
1253"#,
1254                    crate_name,
1255                    crate_name,
1256                    crate_info.version,
1257                    crate_info
1258                        .feature_flags
1259                        .iter()
1260                        .map(|f| format!("- `{}`", f))
1261                        .collect::<Vec<_>>()
1262                        .join("\n"),
1263                    crate_name.replace("-", "_")
1264                )
1265            }
1266            "api.md" => {
1267                let mut api_content = format!("# {} API Reference\n\n", crate_name);
1268
1269                // Group APIs by type
1270                let mut traits = Vec::new();
1271                let mut structs = Vec::new();
1272                let mut functions = Vec::new();
1273                let mut enums = Vec::new();
1274
1275                for api in &crate_info.public_apis {
1276                    match api.item_type {
1277                        ApiItemType::Trait => traits.push(api),
1278                        ApiItemType::Struct => structs.push(api),
1279                        ApiItemType::Function => functions.push(api),
1280                        ApiItemType::Enum => enums.push(api),
1281                        _ => {}
1282                    }
1283                }
1284
1285                if !traits.is_empty() {
1286                    api_content.push_str("## Traits\n\n");
1287                    for trait_api in traits {
1288                        api_content.push_str(&format!("### `{}`\n\n", trait_api.path));
1289                        api_content.push_str(&format!("```rust\n{}\n```\n\n", trait_api.signature));
1290                        if let Some(doc) = &trait_api.documentation {
1291                            api_content.push_str(&format!("{}\n\n", doc));
1292                        }
1293                    }
1294                }
1295
1296                if !structs.is_empty() {
1297                    api_content.push_str("## Structs\n\n");
1298                    for struct_api in structs {
1299                        api_content.push_str(&format!("### `{}`\n\n", struct_api.path));
1300                        api_content
1301                            .push_str(&format!("```rust\n{}\n```\n\n", struct_api.signature));
1302                        if let Some(doc) = &struct_api.documentation {
1303                            api_content.push_str(&format!("{}\n\n", doc));
1304                        }
1305                    }
1306                }
1307
1308                if !functions.is_empty() {
1309                    api_content.push_str("## Functions\n\n");
1310                    for func_api in functions {
1311                        api_content.push_str(&format!("### `{}`\n\n", func_api.path));
1312                        api_content.push_str(&format!("```rust\n{}\n```\n\n", func_api.signature));
1313                        if let Some(doc) = &func_api.documentation {
1314                            api_content.push_str(&format!("{}\n\n", doc));
1315                        }
1316                    }
1317                }
1318
1319                api_content
1320            }
1321            "examples.md" => {
1322                format!(
1323                    r#"# {} Examples
1324
1325## Basic Usage
1326
1327```rust
1328use {}::*;
1329
1330// Add basic example here
1331```
1332
1333## Advanced Usage
1334
1335```rust
1336use {}::*;
1337
1338// Add advanced example here
1339```
1340
1341## Integration Examples
1342
1343```rust
1344// Add integration examples here
1345```
1346"#,
1347                    crate_name,
1348                    crate_name.replace("-", "_"),
1349                    crate_name.replace("-", "_")
1350                )
1351            }
1352            _ => {
1353                format!("# {} {}\n\n[Add content here]\n", crate_name, filename.replace(".md", ""))
1354            }
1355        }
1356    }
1357
1358    /// Generates a simple diff between two strings.
1359    fn generate_simple_diff(&self, original: &str, suggested: &str) -> String {
1360        format!("-{}\n+{}", original, suggested)
1361    }
1362
1363    /// Extracts crate name from API path.
1364    fn extract_crate_name_from_api(&self, api_path: &str) -> String {
1365        // Try to match against known crates first
1366        for crate_name in self.crate_registry.keys() {
1367            let normalized_crate = crate_name.replace("-", "_");
1368            if api_path.starts_with(&normalized_crate) {
1369                return crate_name.clone();
1370            }
1371        }
1372
1373        // Fall back to extracting from path
1374        if let Some(first_part) = api_path.split("::").next() {
1375            first_part.replace("_", "-")
1376        } else {
1377            "unknown".to_string()
1378        }
1379    }
1380}
1381
1382impl Default for SuggestionConfig {
1383    fn default() -> Self {
1384        Self {
1385            min_confidence: 0.7,
1386            max_suggestions_per_issue: 5,
1387            generate_diffs: true,
1388            include_context: true,
1389            enable_caching: true,
1390        }
1391    }
1392}
1393
1394impl SuggestionEngine {
1395    /// Generate suggestions for a specific category of issues.
1396    pub async fn generate_suggestions_for_category(
1397        &self,
1398        category: crate::reporter::IssueCategory,
1399        issues: &[&crate::reporter::AuditIssue],
1400        _crate_registry: &crate::analyzer::CrateRegistry,
1401    ) -> Result<Vec<crate::reporter::Recommendation>> {
1402        use crate::reporter::{IssueCategory, Recommendation, RecommendationType};
1403
1404        let mut recommendations = Vec::new();
1405        let _config = SuggestionConfig::default();
1406
1407        match category {
1408            IssueCategory::ApiMismatch => {
1409                for issue in issues {
1410                    let recommendation = Recommendation {
1411                        id: format!("api-fix-{}", issue.file_path.display()),
1412                        recommendation_type: RecommendationType::FixIssue,
1413                        priority: 1, // High priority
1414                        title: "Fix API Reference".to_string(),
1415                        description: format!(
1416                            "Update API reference in {}",
1417                            issue.file_path.display()
1418                        ),
1419                        affected_files: vec![issue.file_path.clone()],
1420                        estimated_effort_hours: Some(0.5),
1421                        resolves_issues: vec![format!(
1422                            "api-mismatch-{}",
1423                            issue.line_number.unwrap_or(0)
1424                        )],
1425                    };
1426                    recommendations.push(recommendation);
1427                }
1428            }
1429            IssueCategory::VersionInconsistency => {
1430                let recommendation = Recommendation {
1431                    id: format!("version-update-{}", issues.len()),
1432                    recommendation_type: RecommendationType::UpdateContent,
1433                    priority: 2, // Medium priority
1434                    title: "Update Version References".to_string(),
1435                    description: format!(
1436                        "Update {} version references to current workspace version",
1437                        issues.len()
1438                    ),
1439                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1440                    estimated_effort_hours: Some(0.25 * issues.len() as f32),
1441                    resolves_issues: issues
1442                        .iter()
1443                        .enumerate()
1444                        .map(|(i, _)| format!("version-{}", i))
1445                        .collect(),
1446                };
1447                recommendations.push(recommendation);
1448            }
1449            IssueCategory::CompilationError => {
1450                let recommendation = Recommendation {
1451                    id: format!("compilation-fix-{}", issues.len()),
1452                    recommendation_type: RecommendationType::ImproveExamples,
1453                    priority: 1, // High priority
1454                    title: "Fix Compilation Errors".to_string(),
1455                    description: format!(
1456                        "Fix {} compilation errors in code examples",
1457                        issues.len()
1458                    ),
1459                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1460                    estimated_effort_hours: Some(1.0 * issues.len() as f32),
1461                    resolves_issues: issues
1462                        .iter()
1463                        .enumerate()
1464                        .map(|(i, _)| format!("compile-{}", i))
1465                        .collect(),
1466                };
1467                recommendations.push(recommendation);
1468            }
1469            IssueCategory::BrokenLink => {
1470                let recommendation = Recommendation {
1471                    id: format!("link-fix-{}", issues.len()),
1472                    recommendation_type: RecommendationType::FixIssue,
1473                    priority: 2, // Medium priority
1474                    title: "Fix Broken Links".to_string(),
1475                    description: format!("Fix {} broken internal links", issues.len()),
1476                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1477                    estimated_effort_hours: Some(0.1 * issues.len() as f32),
1478                    resolves_issues: issues
1479                        .iter()
1480                        .enumerate()
1481                        .map(|(i, _)| format!("link-{}", i))
1482                        .collect(),
1483                };
1484                recommendations.push(recommendation);
1485            }
1486            IssueCategory::MissingDocumentation => {
1487                let recommendation = Recommendation {
1488                    id: format!("doc-addition-{}", issues.len()),
1489                    recommendation_type: RecommendationType::AddDocumentation,
1490                    priority: 3, // Low priority
1491                    title: "Add Missing Documentation".to_string(),
1492                    description: format!("Document {} undocumented features", issues.len()),
1493                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1494                    estimated_effort_hours: Some(2.0 * issues.len() as f32),
1495                    resolves_issues: issues
1496                        .iter()
1497                        .enumerate()
1498                        .map(|(i, _)| format!("missing-doc-{}", i))
1499                        .collect(),
1500                };
1501                recommendations.push(recommendation);
1502            }
1503            IssueCategory::DeprecatedApi => {
1504                let recommendation = Recommendation {
1505                    id: format!("deprecated-fix-{}", issues.len()),
1506                    recommendation_type: RecommendationType::UpdateContent,
1507                    priority: 2, // Medium priority
1508                    title: "Update Deprecated API References".to_string(),
1509                    description: format!("Update {} deprecated API references", issues.len()),
1510                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1511                    estimated_effort_hours: Some(0.5 * issues.len() as f32),
1512                    resolves_issues: issues
1513                        .iter()
1514                        .enumerate()
1515                        .map(|(i, _)| format!("deprecated-{}", i))
1516                        .collect(),
1517                };
1518                recommendations.push(recommendation);
1519            }
1520            IssueCategory::ProcessingError => {
1521                let recommendation = Recommendation {
1522                    id: format!("processing-fix-{}", issues.len()),
1523                    recommendation_type: RecommendationType::FixIssue,
1524                    priority: 1, // High priority
1525                    title: "Fix Processing Errors".to_string(),
1526                    description: format!("Resolve {} file processing errors", issues.len()),
1527                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1528                    estimated_effort_hours: Some(1.0 * issues.len() as f32),
1529                    resolves_issues: issues
1530                        .iter()
1531                        .enumerate()
1532                        .map(|(i, _)| format!("processing-{}", i))
1533                        .collect(),
1534                };
1535                recommendations.push(recommendation);
1536            }
1537            _ => {
1538                // Generic recommendation for other categories
1539                let recommendation = Recommendation {
1540                    id: format!("generic-fix-{}", issues.len()),
1541                    recommendation_type: RecommendationType::FixIssue,
1542                    priority: 2, // Medium priority
1543                    title: format!("Address {} Issues", category.description()),
1544                    description: format!(
1545                        "Review and fix {} issues of type: {}",
1546                        issues.len(),
1547                        category.description()
1548                    ),
1549                    affected_files: issues.iter().map(|i| i.file_path.clone()).collect(),
1550                    estimated_effort_hours: Some(1.0 * issues.len() as f32),
1551                    resolves_issues: issues
1552                        .iter()
1553                        .enumerate()
1554                        .map(|(i, _)| format!("generic-{}", i))
1555                        .collect(),
1556                };
1557                recommendations.push(recommendation);
1558            }
1559        }
1560
1561        Ok(recommendations)
1562    }
1563}
1564
1565#[cfg(test)]
1566mod tests {
1567    use super::*;
1568    use crate::{Dependency, PublicApi};
1569
1570    fn create_test_crate_info() -> CrateInfo {
1571        CrateInfo {
1572            name: "adk-core".to_string(),
1573            version: "0.1.0".to_string(),
1574            path: PathBuf::from("/tmp/adk-core"),
1575            public_apis: vec![
1576                PublicApi {
1577                    path: "Agent".to_string(),
1578                    signature: "pub trait Agent".to_string(),
1579                    item_type: ApiItemType::Trait,
1580                    documentation: Some("Core agent trait".to_string()),
1581                    deprecated: false,
1582                    source_file: PathBuf::from("src/lib.rs"),
1583                    line_number: 10,
1584                },
1585                PublicApi {
1586                    path: "LlmAgent".to_string(),
1587                    signature: "pub struct LlmAgent".to_string(),
1588                    item_type: ApiItemType::Struct,
1589                    documentation: Some("LLM-based agent".to_string()),
1590                    deprecated: false,
1591                    source_file: PathBuf::from("src/lib.rs"),
1592                    line_number: 20,
1593                },
1594                PublicApi {
1595                    path: "OldAgent".to_string(),
1596                    signature: "pub struct OldAgent".to_string(),
1597                    item_type: ApiItemType::Struct,
1598                    documentation: Some("Deprecated agent".to_string()),
1599                    deprecated: true,
1600                    source_file: PathBuf::from("src/lib.rs"),
1601                    line_number: 30,
1602                },
1603            ],
1604            feature_flags: vec!["default".to_string()],
1605            dependencies: vec![Dependency {
1606                name: "tokio".to_string(),
1607                version: "1.0".to_string(),
1608                features: vec!["full".to_string()],
1609                optional: false,
1610            }],
1611            rust_version: Some("1.85.0".to_string()),
1612        }
1613    }
1614
1615    fn create_test_engine() -> SuggestionEngine {
1616        let mut registry = HashMap::new();
1617        registry.insert("adk-core".to_string(), create_test_crate_info());
1618
1619        SuggestionEngine::new(registry, "0.1.0".to_string())
1620    }
1621
1622    #[test]
1623    fn test_suggestion_engine_creation() {
1624        let engine = create_test_engine();
1625        assert_eq!(engine.workspace_version, "0.1.0");
1626        assert!(engine.crate_registry.contains_key("adk-core"));
1627    }
1628
1629    #[test]
1630    fn test_api_signature_correction() {
1631        let engine = create_test_engine();
1632        let config = SuggestionConfig::default();
1633
1634        let api_ref = ApiReference {
1635            crate_name: "adk-core".to_string(),
1636            item_path: "Agent".to_string(),
1637            item_type: ApiItemType::Trait,
1638            line_number: 10,
1639            context: "use adk_core::Agent;".to_string(),
1640        };
1641
1642        let suggestions = engine
1643            .suggest_api_signature_corrections(&api_ref, Path::new("test.md"), &config)
1644            .unwrap();
1645
1646        assert!(!suggestions.is_empty());
1647        assert_eq!(suggestions[0].suggestion_type, SuggestionType::ApiSignatureCorrection);
1648        assert_eq!(suggestions[0].confidence, 1.0); // Exact match
1649    }
1650
1651    #[test]
1652    fn test_version_correction() {
1653        let engine = create_test_engine();
1654        let config = SuggestionConfig::default();
1655
1656        let version_ref = VersionReference {
1657            version: "0.0.1".to_string(), // Outdated version
1658            version_type: VersionType::CrateVersion,
1659            line_number: 5,
1660            context: "adk-core = \"0.0.1\"".to_string(),
1661        };
1662
1663        let suggestions = engine
1664            .suggest_version_corrections(
1665                &version_ref,
1666                "adk-core", // Pass crate name separately
1667                Path::new("test.md"),
1668                &config,
1669            )
1670            .unwrap();
1671
1672        assert!(!suggestions.is_empty());
1673        assert_eq!(suggestions[0].suggestion_type, SuggestionType::VersionUpdate);
1674        assert_eq!(suggestions[0].suggested_text, "0.1.0");
1675    }
1676
1677    #[test]
1678    fn test_compilation_fix_suggestions() {
1679        let engine = create_test_engine();
1680        let config = SuggestionConfig::default();
1681
1682        let errors = vec![CompilationError {
1683            message: "cannot find adk_core in scope".to_string(),
1684            line: Some(1),
1685            column: Some(5),
1686            error_type: ErrorType::UnresolvedImport,
1687            suggestion: None,
1688            code_snippet: None,
1689        }];
1690
1691        let suggestions =
1692            engine.suggest_compilation_fixes(&errors, Path::new("test.rs"), &config).unwrap();
1693
1694        assert!(!suggestions.is_empty());
1695        assert_eq!(suggestions[0].suggestion_type, SuggestionType::ImportFix);
1696        assert!(suggestions[0].suggested_text.contains("use adk_core"));
1697    }
1698
1699    #[test]
1700    fn test_similarity_calculation() {
1701        let engine = create_test_engine();
1702
1703        assert_eq!(engine.calculate_similarity("Agent", "Agent"), 1.0);
1704        assert!(engine.calculate_similarity("Agent", "Agnt") > 0.6);
1705        assert!(engine.calculate_similarity("Agent", "LlmAgent") > 0.4);
1706        assert!(engine.calculate_similarity("Agent", "CompletelyDifferent") < 0.3);
1707    }
1708
1709    #[test]
1710    fn test_levenshtein_distance() {
1711        let engine = create_test_engine();
1712
1713        assert_eq!(engine.levenshtein_distance("", ""), 0);
1714        assert_eq!(engine.levenshtein_distance("abc", "abc"), 0);
1715        assert_eq!(engine.levenshtein_distance("abc", "ab"), 1);
1716        assert_eq!(engine.levenshtein_distance("abc", "def"), 3);
1717    }
1718
1719    #[test]
1720    fn test_import_fix_suggestions() {
1721        let engine = create_test_engine();
1722
1723        assert_eq!(
1724            engine.suggest_import_fix("cannot find adk_core"),
1725            Some("use adk_core::*;".to_string())
1726        );
1727        assert_eq!(engine.suggest_import_fix("cannot find tokio"), Some("use tokio;".to_string()));
1728        assert_eq!(engine.suggest_import_fix("cannot find unknown_crate"), None);
1729    }
1730
1731    #[test]
1732    fn test_dependency_addition_suggestions() {
1733        let engine = create_test_engine();
1734
1735        assert!(
1736            engine.suggest_dependency_addition("missing adk_core").unwrap().contains("adk-core")
1737        );
1738        assert!(engine.suggest_dependency_addition("missing tokio").unwrap().contains("tokio"));
1739        assert_eq!(engine.suggest_dependency_addition("missing unknown"), None);
1740    }
1741
1742    #[test]
1743    fn test_async_pattern_fix_suggestions() {
1744        let engine = create_test_engine();
1745
1746        let suggestions = engine.suggest_async_pattern_fixes("async fn main not supported");
1747        assert!(suggestions.iter().any(|s| s.contains("tokio::main")));
1748
1749        let suggestions = engine.suggest_async_pattern_fixes("missing await");
1750        assert!(suggestions.iter().any(|s| s.contains("await")));
1751    }
1752
1753    #[test]
1754    fn test_diff_generation() {
1755        let engine = create_test_engine();
1756
1757        let diff = engine.generate_simple_diff("old_text", "new_text");
1758        assert!(diff.contains("-old_text"));
1759        assert!(diff.contains("+new_text"));
1760    }
1761
1762    #[test]
1763    fn test_suggestion_config_defaults() {
1764        let config = SuggestionConfig::default();
1765
1766        assert_eq!(config.min_confidence, 0.7);
1767        assert_eq!(config.max_suggestions_per_issue, 5);
1768        assert!(config.generate_diffs);
1769        assert!(config.include_context);
1770        assert!(config.enable_caching);
1771    }
1772
1773    #[test]
1774    fn test_deprecated_api_detection() {
1775        let engine = create_test_engine();
1776        let config = SuggestionConfig::default();
1777
1778        let api_ref = ApiReference {
1779            crate_name: "adk-core".to_string(),
1780            item_path: "OldAgent".to_string(),
1781            item_type: ApiItemType::Struct,
1782            line_number: 15,
1783            context: "use adk_core::OldAgent;".to_string(),
1784        };
1785
1786        let suggestions = engine
1787            .suggest_api_signature_corrections(&api_ref, Path::new("test.md"), &config)
1788            .unwrap();
1789
1790        // Should find exact match first (even if deprecated), then suggest replacement
1791        assert!(!suggestions.is_empty());
1792        // The deprecated API should be found as an exact match, and then a replacement should be suggested
1793        let has_deprecated_replacement = suggestions
1794            .iter()
1795            .any(|s| s.suggestion_type == SuggestionType::DeprecatedApiReplacement);
1796        let has_exact_match =
1797            suggestions.iter().any(|s| s.suggestion_type == SuggestionType::ApiSignatureCorrection);
1798
1799        // Should have either an exact match or a deprecated replacement
1800        assert!(has_deprecated_replacement || has_exact_match);
1801    }
1802
1803    #[test]
1804    fn test_fuzzy_matching() {
1805        let engine = create_test_engine();
1806        let config = SuggestionConfig::default();
1807
1808        let api_ref = ApiReference {
1809            crate_name: "adk-core".to_string(),
1810            item_path: "Agnt".to_string(), // Typo in "Agent"
1811            item_type: ApiItemType::Trait,
1812            line_number: 20,
1813            context: "use adk_core::Agnt;".to_string(),
1814        };
1815
1816        let suggestions = engine
1817            .suggest_api_signature_corrections(&api_ref, Path::new("test.md"), &config)
1818            .unwrap();
1819
1820        assert!(!suggestions.is_empty());
1821        assert!(suggestions[0].suggested_text.contains("Agent"));
1822        assert!(suggestions[0].confidence > 0.7);
1823    }
1824
1825    #[test]
1826    fn test_documentation_placement_suggestions() {
1827        let engine = create_test_engine();
1828        let config = SuggestionConfig::default();
1829
1830        let undocumented_apis = vec![PublicApi {
1831            path: "NewAgent".to_string(),
1832            signature: "pub struct NewAgent".to_string(),
1833            item_type: ApiItemType::Struct,
1834            documentation: None,
1835            deprecated: false,
1836            source_file: PathBuf::from("src/lib.rs"),
1837            line_number: 40,
1838        }];
1839
1840        let workspace_path = Path::new("/tmp/workspace");
1841        let docs_path = Path::new("/tmp/docs");
1842
1843        let suggestions = engine
1844            .suggest_documentation_placement(&undocumented_apis, workspace_path, docs_path, &config)
1845            .unwrap();
1846
1847        assert!(!suggestions.is_empty());
1848        assert_eq!(suggestions[0].suggestion_type, SuggestionType::StructureImprovement);
1849        assert!(suggestions[0].description.contains("NewAgent"));
1850    }
1851
1852    #[test]
1853    fn test_structure_improvement_suggestions() {
1854        let engine = create_test_engine();
1855        let config = SuggestionConfig::default();
1856
1857        let docs_path = Path::new("/tmp/nonexistent_docs");
1858
1859        let suggestions = engine.suggest_structure_improvements(docs_path, &config).unwrap();
1860
1861        assert!(!suggestions.is_empty());
1862        // Should suggest creating essential files
1863        assert!(suggestions.iter().any(|s| s.description.contains("Getting Started")));
1864        assert!(suggestions.iter().any(|s| s.description.contains("API Reference")));
1865    }
1866
1867    #[test]
1868    fn test_crate_name_extraction() {
1869        let engine = create_test_engine();
1870
1871        assert_eq!(engine.extract_crate_name_from_api("adk_core::Agent"), "adk-core");
1872        assert_eq!(engine.extract_crate_name_from_api("Agent"), "Agent"); // No conversion for single names
1873        assert_eq!(engine.extract_crate_name_from_api("some_module::SomeStruct"), "some-module");
1874    }
1875
1876    #[test]
1877    fn test_documentation_section_determination() {
1878        let engine = create_test_engine();
1879
1880        let trait_api = PublicApi {
1881            path: "TestTrait".to_string(),
1882            signature: "pub trait TestTrait".to_string(),
1883            item_type: ApiItemType::Trait,
1884            documentation: None,
1885            deprecated: false,
1886            source_file: PathBuf::from("src/lib.rs"),
1887            line_number: 50,
1888        };
1889
1890        assert_eq!(engine.determine_documentation_section(&trait_api), "Traits");
1891
1892        let struct_api = PublicApi {
1893            path: "TestStruct".to_string(),
1894            signature: "pub struct TestStruct".to_string(),
1895            item_type: ApiItemType::Struct,
1896            documentation: None,
1897            deprecated: false,
1898            source_file: PathBuf::from("src/lib.rs"),
1899            line_number: 60,
1900        };
1901
1902        assert_eq!(engine.determine_documentation_section(&struct_api), "Structs");
1903    }
1904
1905    #[test]
1906    fn test_documentation_template_generation() {
1907        let engine = create_test_engine();
1908
1909        let api = PublicApi {
1910            path: "TestStruct".to_string(),
1911            signature: "pub struct TestStruct { field: String }".to_string(),
1912            item_type: ApiItemType::Struct,
1913            documentation: Some("A test structure".to_string()),
1914            deprecated: false,
1915            source_file: PathBuf::from("src/lib.rs"),
1916            line_number: 70,
1917        };
1918
1919        let template = engine.generate_documentation_template(&api);
1920
1921        assert!(template.contains("## Structs"));
1922        assert!(template.contains("### `TestStruct`"));
1923        assert!(template.contains("A test structure"));
1924        assert!(template.contains("pub struct TestStruct"));
1925    }
1926
1927    #[test]
1928    fn test_file_template_generation() {
1929        let engine = create_test_engine();
1930
1931        let getting_started = engine.generate_file_template("getting-started.md");
1932        assert!(getting_started.contains("# Getting Started"));
1933        assert!(getting_started.contains("## Installation"));
1934
1935        let api_ref = engine.generate_file_template("api-reference.md");
1936        assert!(api_ref.contains("# API Reference"));
1937        assert!(api_ref.contains("## Traits"));
1938
1939        let examples = engine.generate_file_template("examples.md");
1940        assert!(examples.contains("# Examples and Tutorials"));
1941        assert!(examples.contains("## Basic Examples"));
1942    }
1943
1944    #[test]
1945    fn test_crate_file_template_generation() {
1946        let engine = create_test_engine();
1947        let crate_info = create_test_crate_info();
1948
1949        let readme = engine.generate_crate_file_template("adk-core", "README.md", &crate_info);
1950        assert!(readme.contains("# adk-core"));
1951        assert!(readme.contains("## Installation"));
1952        assert!(readme.contains("adk-core = \"0.1.0\""));
1953
1954        let api_doc = engine.generate_crate_file_template("adk-core", "api.md", &crate_info);
1955        assert!(api_doc.contains("# adk-core API Reference"));
1956        assert!(api_doc.contains("## Traits"));
1957        assert!(api_doc.contains("### `Agent`"));
1958    }
1959
1960    #[test]
1961    fn test_index_template_generation() {
1962        let engine = create_test_engine();
1963        let docs_path = Path::new("/tmp/docs");
1964
1965        let index = engine.generate_index_template(docs_path).unwrap();
1966
1967        assert!(index.contains("# ADK-Rust Documentation"));
1968        assert!(index.contains("## Getting Started"));
1969        assert!(index.contains("## Crates"));
1970        // The crate names should be listed even if directories don't exist
1971        assert!(index.contains("adk-core") || index.contains("- [adk-core]"));
1972    }
1973}