1use 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#[derive(Debug)]
17pub struct SuggestionEngine {
18 crate_registry: HashMap<String, CrateInfo>,
20 workspace_version: String,
22 suggestion_cache: HashMap<String, Vec<Suggestion>>,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct Suggestion {
29 pub suggestion_type: SuggestionType,
31 pub description: String,
33 pub original_text: String,
35 pub suggested_text: String,
37 pub file_path: PathBuf,
39 pub line_number: Option<usize>,
41 pub column_number: Option<usize>,
43 pub confidence: f64,
45 pub context: Option<String>,
47 pub diff: Option<String>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum SuggestionType {
54 ApiSignatureCorrection,
56 VersionUpdate,
58 CompilationFix,
60 ImportFix,
62 DependencyAddition,
64 DeprecatedApiReplacement,
66 AsyncPatternFix,
68 LinkCorrection,
70 FeatureFlagCorrection,
72 StructureImprovement,
74}
75
76#[derive(Debug, Clone)]
78pub struct SuggestionConfig {
79 pub min_confidence: f64,
81 pub max_suggestions_per_issue: usize,
83 pub generate_diffs: bool,
85 pub include_context: bool,
87 pub enable_caching: bool,
89}
90
91impl SuggestionEngine {
92 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 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 #[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 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 if let Some(crate_info) = self.crate_registry.get(&api_ref.crate_name) {
145 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, config,
153 )?;
154 suggestions.push(suggestion);
155 } else {
156 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 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 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 suggestions.truncate(config.max_suggestions_per_issue);
222
223 Ok(suggestions)
224 }
225
226 #[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 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 self.get_workspace_rust_version().unwrap_or_else(|| "1.85.0".to_string())
260 }
261 VersionType::WorkspaceVersion => {
262 self.workspace_version.clone()
264 }
265 VersionType::Generic => {
266 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 #[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 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 suggestions.truncate(config.max_suggestions_per_issue);
451
452 Ok(suggestions)
453 }
454
455 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 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 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 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 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 similar_apis.push((api, similarity));
532 }
533 }
534
535 similar_apis.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
537 similar_apis
538 }
539
540 fn find_deprecated_replacement<'a>(
542 &self,
543 crate_info: &'a CrateInfo,
544 api_ref: &ApiReference,
545 ) -> Option<&'a PublicApi> {
546 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 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 fn api_types_match(&self, type1: &ApiItemType, type2: &ApiItemType) -> bool {
588 type1 == type2
589 }
590
591 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 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 fn get_workspace_rust_version(&self) -> Option<String> {
641 Some("1.85.0".to_string())
644 }
645
646 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 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 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 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 fn suggest_deprecated_api_replacement(&self, _error_message: &str) -> Option<String> {
707 Some("Check the latest documentation for the current API".to_string())
710 }
711
712 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 #[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 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 let structure_suggestions = self.suggest_structure_improvements(docs_path, config)?;
776 suggestions.extend(structure_suggestions);
777
778 suggestions.truncate(config.max_suggestions_per_issue);
780
781 Ok(suggestions)
782 }
783
784 #[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 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 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 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 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 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 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 fn determine_documentation_file(&self, api: &PublicApi, docs_path: &Path) -> Result<PathBuf> {
936 let crate_name = self.extract_crate_name_from_api(&api.path);
938
939 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 Ok(docs_path.join("api-reference.md"))
956 }
957 }
958
959 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 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 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 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 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 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 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 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 fn generate_simple_diff(&self, original: &str, suggested: &str) -> String {
1360 format!("-{}\n+{}", original, suggested)
1361 }
1362
1363 fn extract_crate_name_from_api(&self, api_path: &str) -> String {
1365 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 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 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, 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, 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, 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, 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, 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, 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, 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 let recommendation = Recommendation {
1540 id: format!("generic-fix-{}", issues.len()),
1541 recommendation_type: RecommendationType::FixIssue,
1542 priority: 2, 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); }
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(), 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", 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 assert!(!suggestions.is_empty());
1792 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 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(), 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 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"); 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 assert!(index.contains("adk-core") || index.contains("- [adk-core]"));
1972 }
1973}