Skip to main content

omnivore_cli/git/
detector.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CodebaseInfo {
9    pub project_type: ProjectType,
10    pub languages: Vec<Language>,
11    pub frameworks: Vec<Framework>,
12    pub build_tools: Vec<BuildTool>,
13    pub main_language: Option<Language>,
14    pub description: String,
15}
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub enum ProjectType {
19    WebApplication,
20    Library,
21    CLI,
22    MobileApp,
23    API,
24    Documentation,
25    Monorepo,
26    Unknown,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum Language {
31    Rust,
32    JavaScript,
33    TypeScript,
34    Python,
35    Go,
36    Java,
37    CSharp,
38    CPlusPlus,
39    C,
40    Ruby,
41    PHP,
42    Swift,
43    Kotlin,
44    Scala,
45    Elixir,
46    Haskell,
47    Shell,
48    HTML,
49    CSS,
50    Other(String),
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub enum Framework {
55    NextJS,
56    React,
57    Vue,
58    Angular,
59    Svelte,
60    Django,
61    Flask,
62    FastAPI,
63    Rails,
64    Laravel,
65    Spring,
66    Express,
67    NestJS,
68    Actix,
69    Rocket,
70    Gin,
71    Echo,
72    DotNet,
73    Flutter,
74    ReactNative,
75    Other(String),
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub enum BuildTool {
80    Npm,
81    Yarn,
82    Pnpm,
83    Cargo,
84    Maven,
85    Gradle,
86    Pip,
87    Poetry,
88    Composer,
89    Bundler,
90    Go,
91    DotNet,
92    Make,
93    CMake,
94    Other(String),
95}
96
97pub struct CodebaseDetector {
98    root_path: PathBuf,
99}
100
101impl CodebaseDetector {
102    pub fn new(root_path: PathBuf) -> Self {
103        Self { root_path }
104    }
105
106    pub fn detect(&self) -> Result<CodebaseInfo> {
107        let mut info = CodebaseInfo {
108            project_type: ProjectType::Unknown,
109            languages: Vec::new(),
110            frameworks: Vec::new(),
111            build_tools: Vec::new(),
112            main_language: None,
113            description: String::new(),
114        };
115
116        self.detect_by_config_files(&mut info)?;
117        self.detect_by_file_extensions(&mut info)?;
118        self.determine_project_type(&mut info);
119        self.determine_main_language(&mut info);
120        self.generate_description(&mut info);
121
122        Ok(info)
123    }
124
125    fn detect_by_config_files(&self, info: &mut CodebaseInfo) -> Result<()> {
126        let config_checks: Vec<(&str, fn(&Path, &mut CodebaseInfo) -> Result<()>)> = vec![
127            ("package.json", Self::check_nodejs_project),
128            ("Cargo.toml", Self::check_rust_project),
129            ("go.mod", Self::check_go_project),
130            ("requirements.txt", Self::check_python_requirements),
131            ("pyproject.toml", Self::check_python_pyproject),
132            ("Gemfile", Self::check_ruby_project),
133            ("composer.json", Self::check_php_project),
134            ("pom.xml", Self::check_maven_project),
135            ("build.gradle", Self::check_gradle_project),
136            (".csproj", Self::check_dotnet_project),
137            ("CMakeLists.txt", Self::check_cmake_project),
138            ("Makefile", Self::check_makefile_project),
139        ];
140
141        for (file, checker) in config_checks {
142            let path = self.root_path.join(file);
143            if path.exists() {
144                checker(&path, info)?;
145            } else if file.contains('.') {
146                for entry in fs::read_dir(&self.root_path)? {
147                    let entry = entry?;
148                    let filename = entry.file_name();
149                    let filename_str = filename.to_string_lossy();
150                    if filename_str.ends_with(file) {
151                        checker(&entry.path(), info)?;
152                    }
153                }
154            }
155        }
156
157        Ok(())
158    }
159
160    fn check_nodejs_project(path: &Path, info: &mut CodebaseInfo) -> Result<()> {
161        let content = fs::read_to_string(path)?;
162        let json: serde_json::Value = serde_json::from_str(&content)?;
163
164        if !info.languages.contains(&Language::JavaScript) {
165            info.languages.push(Language::JavaScript);
166        }
167
168        if json.get("dependencies").is_some() || json.get("devDependencies").is_some() {
169            if !info.build_tools.contains(&BuildTool::Npm) {
170                info.build_tools.push(BuildTool::Npm);
171            }
172        }
173
174        let deps = json.get("dependencies").and_then(|d| d.as_object());
175        let dev_deps = json.get("devDependencies").and_then(|d| d.as_object());
176
177        let check_dep = |name: &str| -> bool {
178            deps.map_or(false, |d| d.contains_key(name))
179                || dev_deps.map_or(false, |d| d.contains_key(name))
180        };
181
182        if check_dep("next") {
183            info.frameworks.push(Framework::NextJS);
184            info.languages.push(Language::TypeScript);
185        } else if check_dep("react") {
186            info.frameworks.push(Framework::React);
187        } else if check_dep("vue") {
188            info.frameworks.push(Framework::Vue);
189        } else if check_dep("@angular/core") {
190            info.frameworks.push(Framework::Angular);
191            info.languages.push(Language::TypeScript);
192        } else if check_dep("svelte") {
193            info.frameworks.push(Framework::Svelte);
194        } else if check_dep("express") {
195            info.frameworks.push(Framework::Express);
196        } else if check_dep("@nestjs/core") {
197            info.frameworks.push(Framework::NestJS);
198            info.languages.push(Language::TypeScript);
199        } else if check_dep("react-native") {
200            info.frameworks.push(Framework::ReactNative);
201        }
202
203        if path.parent().and_then(|p| p.join("yarn.lock").exists().then_some(())).is_some() {
204            info.build_tools.push(BuildTool::Yarn);
205        }
206        if path.parent().and_then(|p| p.join("pnpm-lock.yaml").exists().then_some(())).is_some() {
207            info.build_tools.push(BuildTool::Pnpm);
208        }
209
210        Ok(())
211    }
212
213    fn check_rust_project(path: &Path, info: &mut CodebaseInfo) -> Result<()> {
214        let content = fs::read_to_string(path)?;
215        let toml: toml::Value = toml::from_str(&content)?;
216
217        info.languages.push(Language::Rust);
218        info.build_tools.push(BuildTool::Cargo);
219
220        if let Some(deps) = toml.get("dependencies").and_then(|d| d.as_table()) {
221            if deps.contains_key("actix-web") {
222                info.frameworks.push(Framework::Actix);
223            } else if deps.contains_key("rocket") {
224                info.frameworks.push(Framework::Rocket);
225            }
226        }
227
228        Ok(())
229    }
230
231    fn check_go_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
232        info.languages.push(Language::Go);
233        info.build_tools.push(BuildTool::Go);
234        Ok(())
235    }
236
237    fn check_python_requirements(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
238        info.languages.push(Language::Python);
239        info.build_tools.push(BuildTool::Pip);
240        Ok(())
241    }
242
243    fn check_python_pyproject(path: &Path, info: &mut CodebaseInfo) -> Result<()> {
244        let content = fs::read_to_string(path)?;
245        let toml: toml::Value = toml::from_str(&content)?;
246
247        info.languages.push(Language::Python);
248
249        if toml.get("tool").and_then(|t| t.get("poetry")).is_some() {
250            info.build_tools.push(BuildTool::Poetry);
251        } else {
252            info.build_tools.push(BuildTool::Pip);
253        }
254
255        if let Some(deps) = toml
256            .get("tool")
257            .and_then(|t| t.get("poetry"))
258            .and_then(|p| p.get("dependencies"))
259            .and_then(|d| d.as_table())
260        {
261            if deps.contains_key("django") {
262                info.frameworks.push(Framework::Django);
263            } else if deps.contains_key("flask") {
264                info.frameworks.push(Framework::Flask);
265            } else if deps.contains_key("fastapi") {
266                info.frameworks.push(Framework::FastAPI);
267            }
268        }
269
270        Ok(())
271    }
272
273    fn check_ruby_project(path: &Path, info: &mut CodebaseInfo) -> Result<()> {
274        info.languages.push(Language::Ruby);
275        info.build_tools.push(BuildTool::Bundler);
276        
277        // Check for Rails by looking for config/application.rb relative to Gemfile
278        if let Some(parent) = path.parent() {
279            if parent.join("config").join("application.rb").exists() {
280                info.frameworks.push(Framework::Rails);
281            }
282        }
283        
284        Ok(())
285    }
286
287    fn check_php_project(path: &Path, info: &mut CodebaseInfo) -> Result<()> {
288        let content = fs::read_to_string(path)?;
289        let json: serde_json::Value = serde_json::from_str(&content)?;
290
291        info.languages.push(Language::PHP);
292        info.build_tools.push(BuildTool::Composer);
293
294        if let Some(require) = json.get("require").and_then(|r| r.as_object()) {
295            if require.contains_key("laravel/framework") {
296                info.frameworks.push(Framework::Laravel);
297            }
298        }
299
300        Ok(())
301    }
302
303    fn check_maven_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
304        info.languages.push(Language::Java);
305        info.build_tools.push(BuildTool::Maven);
306        Ok(())
307    }
308
309    fn check_gradle_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
310        info.languages.push(Language::Java);
311        info.build_tools.push(BuildTool::Gradle);
312        Ok(())
313    }
314
315    fn check_dotnet_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
316        info.languages.push(Language::CSharp);
317        info.build_tools.push(BuildTool::DotNet);
318        info.frameworks.push(Framework::DotNet);
319        Ok(())
320    }
321
322    fn check_cmake_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
323        info.languages.push(Language::CPlusPlus);
324        info.build_tools.push(BuildTool::CMake);
325        Ok(())
326    }
327
328    fn check_makefile_project(_path: &Path, info: &mut CodebaseInfo) -> Result<()> {
329        if !info.build_tools.contains(&BuildTool::Make) {
330            info.build_tools.push(BuildTool::Make);
331        }
332        Ok(())
333    }
334
335    fn detect_by_file_extensions(&self, info: &mut CodebaseInfo) -> Result<()> {
336        let mut language_counts: HashMap<Language, usize> = HashMap::new();
337
338        for entry in walkdir::WalkDir::new(&self.root_path)
339            .max_depth(3)
340            .into_iter()
341            .filter_map(|e| e.ok())
342            .filter(|e| e.file_type().is_file())
343        {
344            if let Some(ext) = entry.path().extension().and_then(|e| e.to_str()) {
345                let lang = match ext {
346                    "rs" => Some(Language::Rust),
347                    "js" | "mjs" | "cjs" => Some(Language::JavaScript),
348                    "ts" | "tsx" => Some(Language::TypeScript),
349                    "py" => Some(Language::Python),
350                    "go" => Some(Language::Go),
351                    "java" => Some(Language::Java),
352                    "cs" => Some(Language::CSharp),
353                    "cpp" | "cc" | "cxx" => Some(Language::CPlusPlus),
354                    "c" | "h" => Some(Language::C),
355                    "rb" => Some(Language::Ruby),
356                    "php" => Some(Language::PHP),
357                    "swift" => Some(Language::Swift),
358                    "kt" | "kts" => Some(Language::Kotlin),
359                    "scala" => Some(Language::Scala),
360                    "ex" | "exs" => Some(Language::Elixir),
361                    "hs" => Some(Language::Haskell),
362                    "sh" | "bash" | "zsh" => Some(Language::Shell),
363                    "html" | "htm" => Some(Language::HTML),
364                    "css" | "scss" | "sass" | "less" => Some(Language::CSS),
365                    _ => None,
366                };
367
368                if let Some(lang) = lang {
369                    *language_counts.entry(lang.clone()).or_insert(0) += 1;
370                    if !info.languages.contains(&lang) {
371                        info.languages.push(lang);
372                    }
373                }
374            }
375        }
376
377        if let Some((main_lang, _)) = language_counts.iter().max_by_key(|(_, count)| *count) {
378            info.main_language = Some(main_lang.clone());
379        }
380
381        Ok(())
382    }
383
384    fn determine_project_type(&self, info: &mut CodebaseInfo) {
385        if !info.frameworks.is_empty() {
386            if matches!(
387                info.frameworks.first(),
388                Some(Framework::NextJS | Framework::React | Framework::Vue | Framework::Angular | Framework::Svelte)
389            ) {
390                info.project_type = ProjectType::WebApplication;
391            } else if matches!(
392                info.frameworks.first(),
393                Some(Framework::Express | Framework::FastAPI | Framework::Django | Framework::Flask | Framework::Actix | Framework::Rocket)
394            ) {
395                info.project_type = ProjectType::API;
396            } else if matches!(
397                info.frameworks.first(),
398                Some(Framework::ReactNative | Framework::Flutter)
399            ) {
400                info.project_type = ProjectType::MobileApp;
401            }
402        } else if self.root_path.join("src").join("main.rs").exists()
403            || self.root_path.join("cmd").exists()
404            || self.root_path.join("cli.py").exists()
405        {
406            info.project_type = ProjectType::CLI;
407        } else if self.root_path.join("lib.rs").exists()
408            || self.root_path.join("index.js").exists()
409            || self.root_path.join("__init__.py").exists()
410        {
411            info.project_type = ProjectType::Library;
412        } else if self.root_path.join("docs").exists()
413            || self.root_path.join("README.md").exists()
414        {
415            info.project_type = ProjectType::Documentation;
416        } else if self.root_path.join("packages").exists()
417            || self.root_path.join("apps").exists()
418        {
419            info.project_type = ProjectType::Monorepo;
420        }
421    }
422
423    fn determine_main_language(&self, info: &mut CodebaseInfo) {
424        if info.main_language.is_none() && !info.languages.is_empty() {
425            info.main_language = Some(info.languages[0].clone());
426        }
427    }
428
429    fn generate_description(&self, info: &mut CodebaseInfo) {
430        let project_type = match &info.project_type {
431            ProjectType::WebApplication => "web application",
432            ProjectType::Library => "library",
433            ProjectType::CLI => "command-line tool",
434            ProjectType::MobileApp => "mobile application",
435            ProjectType::API => "API service",
436            ProjectType::Documentation => "documentation project",
437            ProjectType::Monorepo => "monorepo",
438            ProjectType::Unknown => "project",
439        };
440
441        let main_lang = info
442            .main_language
443            .as_ref()
444            .map(|l| format!("{:?}", l))
445            .unwrap_or_else(|| "Unknown".to_string());
446
447        let framework_str = if !info.frameworks.is_empty() {
448            format!(" using {:?}", info.frameworks[0])
449        } else {
450            String::new()
451        };
452
453        info.description = format!(
454            "{} {} written in {}{}",
455            if matches!(info.project_type, ProjectType::API | ProjectType::Unknown) {
456                "A"
457            } else {
458                "An"
459            },
460            project_type,
461            main_lang,
462            framework_str
463        );
464    }
465}
466
467pub fn get_default_include_patterns(info: &CodebaseInfo) -> Vec<String> {
468    let mut patterns = Vec::new();
469
470    for lang in &info.languages {
471        match lang {
472            Language::Rust => {
473                patterns.extend(vec![
474                    "**/*.rs".to_string(),
475                    "**/Cargo.toml".to_string(),
476                    "**/Cargo.lock".to_string(),
477                ]);
478            }
479            Language::JavaScript | Language::TypeScript => {
480                patterns.extend(vec![
481                    "**/*.js".to_string(),
482                    "**/*.jsx".to_string(),
483                    "**/*.ts".to_string(),
484                    "**/*.tsx".to_string(),
485                    "**/*.mjs".to_string(),
486                    "**/*.cjs".to_string(),
487                    "**/package.json".to_string(),
488                    "**/tsconfig.json".to_string(),
489                    "**/.eslintrc.json".to_string(),
490                    "**/.babelrc".to_string(),
491                    "**/webpack.config.js".to_string(),
492                ]);
493            }
494            Language::Python => {
495                patterns.extend(vec![
496                    "**/*.py".to_string(),
497                    "**/requirements.txt".to_string(),
498                    "**/pyproject.toml".to_string(),
499                    "**/setup.py".to_string(),
500                ]);
501            }
502            Language::Go => {
503                patterns.extend(vec![
504                    "**/*.go".to_string(),
505                    "**/go.mod".to_string(),
506                    "**/go.sum".to_string(),
507                ]);
508            }
509            Language::Java => {
510                patterns.extend(vec![
511                    "**/*.java".to_string(),
512                    "**/pom.xml".to_string(),
513                    "**/build.gradle".to_string(),
514                ]);
515            }
516            Language::CSharp => {
517                patterns.extend(vec![
518                    "**/*.cs".to_string(),
519                    "**/*.csproj".to_string(),
520                    "**/*.sln".to_string(),
521                ]);
522            }
523            Language::Ruby => {
524                patterns.extend(vec![
525                    "**/*.rb".to_string(),
526                    "**/Gemfile".to_string(),
527                    "**/Gemfile.lock".to_string(),
528                ]);
529            }
530            Language::PHP => {
531                patterns.extend(vec![
532                    "**/*.php".to_string(),
533                    "**/composer.json".to_string(),
534                    "**/composer.lock".to_string(),
535                ]);
536            }
537            _ => {}
538        }
539    }
540
541    patterns.extend(vec![
542        "**/README.md".to_string(),
543        "**/.env.example".to_string(),
544        "**/Dockerfile".to_string(),
545        "**/docker-compose.yml".to_string(),
546        "**/.gitignore".to_string(),
547    ]);
548
549    patterns.sort();
550    patterns.dedup();
551    patterns
552}
553
554pub fn get_smart_exclude_patterns(info: &CodebaseInfo) -> Vec<String> {
555    let mut patterns = vec![
556        // Package and dependency directories (NEVER needed for code analysis)
557        "**/node_modules/**".to_string(),
558        "**/bower_components/**".to_string(),
559        "**/jspm_packages/**".to_string(),
560        "**/vendor/**".to_string(),
561        "**/.pnp/**".to_string(),
562        "**/.yarn/**".to_string(),
563        
564        // Build outputs
565        "**/target/**".to_string(),
566        "**/dist/**".to_string(),
567        "**/build/**".to_string(),
568        "**/out/**".to_string(),
569        "**/output/**".to_string(),
570        "**/.next/**".to_string(),
571        "**/.nuxt/**".to_string(),
572        "**/.output/**".to_string(),
573        "**/.svelte-kit/**".to_string(),
574        "**/public/build/**".to_string(),
575        
576        // Version control
577        "**/.git/**".to_string(),
578        "**/.svn/**".to_string(),
579        "**/.hg/**".to_string(),
580        
581        // Python specific
582        "**/__pycache__/**".to_string(),
583        "**/venv/**".to_string(),
584        "**/.venv/**".to_string(),
585        "**/env/**".to_string(),
586        "**/.env/**".to_string(),
587        "**/site-packages/**".to_string(),
588        "**/.tox/**".to_string(),
589        "**/*.egg-info/**".to_string(),
590        "**/pip-wheel-metadata/**".to_string(),
591        
592        // Test coverage and reports
593        "**/coverage/**".to_string(),
594        "**/.coverage/**".to_string(),
595        "**/htmlcov/**".to_string(),
596        "**/.nyc_output/**".to_string(),
597        "**/test-results/**".to_string(),
598        "**/.pytest_cache/**".to_string(),
599        
600        // IDE and editor files
601        "**/.idea/**".to_string(),
602        "**/.vscode/**".to_string(),
603        "**/.vs/**".to_string(),
604        "**/*.swp".to_string(),
605        "**/*.swo".to_string(),
606        "**/*~".to_string(),
607        "**/.DS_Store".to_string(),
608        "**/Thumbs.db".to_string(),
609        
610        // Logs and temporary files
611        "**/logs/**".to_string(),
612        "**/*.log".to_string(),
613        "**/tmp/**".to_string(),
614        "**/temp/**".to_string(),
615        "**/.tmp/**".to_string(),
616        "**/.temp/**".to_string(),
617        "**/.cache/**".to_string(),
618        
619        // Minified and compiled files
620        "**/*.min.js".to_string(),
621        "**/*.min.css".to_string(),
622        "**/*.map".to_string(),
623        "**/bundle.js".to_string(),
624        "**/chunk.*.js".to_string(),
625        "**/*.bundle.js".to_string(),
626        
627        // Lock files (usually not needed for code understanding)
628        "**/package-lock.json".to_string(),
629        "**/yarn.lock".to_string(),
630        "**/pnpm-lock.yaml".to_string(),
631        "**/composer.lock".to_string(),
632        "**/Gemfile.lock".to_string(),
633        "**/poetry.lock".to_string(),
634        "**/Pipfile.lock".to_string(),
635        
636        // Documentation build outputs
637        "**/docs/_build/**".to_string(),
638        "**/site/**".to_string(),
639        "**/_site/**".to_string(),
640        
641        // Database files
642        "**/*.sqlite".to_string(),
643        "**/*.sqlite3".to_string(),
644        "**/*.db".to_string(),
645        "**/*.mdb".to_string(),
646        "**/*.accdb".to_string(),
647        
648        // Large binary and archive files
649        "**/*.zip".to_string(),
650        "**/*.tar".to_string(),
651        "**/*.tar.gz".to_string(),
652        "**/*.tgz".to_string(),
653        "**/*.rar".to_string(),
654        "**/*.7z".to_string(),
655        "**/*.gz".to_string(),
656        "**/*.bz2".to_string(),
657        "**/*.xz".to_string(),
658        "**/*.jar".to_string(),
659        "**/*.war".to_string(),
660        "**/*.ear".to_string(),
661        "**/*.deb".to_string(),
662        "**/*.rpm".to_string(),
663        "**/*.dmg".to_string(),
664        "**/*.pkg".to_string(),
665        "**/*.iso".to_string(),
666        
667        // Machine learning model files (often very large)
668        "**/*.pt".to_string(),
669        "**/*.pth".to_string(),
670        "**/*.pkl".to_string(),
671        "**/*.pickle".to_string(),
672        "**/*.h5".to_string(),
673        "**/*.hdf5".to_string(),
674        "**/*.pb".to_string(),
675        "**/*.onnx".to_string(),
676        "**/*.tflite".to_string(),
677        "**/*.caffemodel".to_string(),
678        "**/*.weights".to_string(),
679        "**/*.model".to_string(),
680        "**/*.ckpt".to_string(),
681        "**/*.safetensors".to_string(),
682        
683        // Data files (often large and not code)
684        "**/*.csv".to_string(),
685        "**/*.tsv".to_string(),
686        "**/*.parquet".to_string(),
687        "**/*.feather".to_string(),
688        "**/*.msgpack".to_string(),
689        "**/*.npy".to_string(),
690        "**/*.npz".to_string(),
691        
692        // Office documents
693        "**/*.doc".to_string(),
694        "**/*.docx".to_string(),
695        "**/*.xls".to_string(),
696        "**/*.xlsx".to_string(),
697        "**/*.ppt".to_string(),
698        "**/*.pptx".to_string(),
699        "**/*.pdf".to_string(),
700        "**/*.odt".to_string(),
701        "**/*.ods".to_string(),
702        "**/*.odp".to_string(),
703        
704        // Media files (usually not needed for code analysis)
705        "**/*.jpg".to_string(),
706        "**/*.jpeg".to_string(),
707        "**/*.png".to_string(),
708        "**/*.gif".to_string(),
709        "**/*.bmp".to_string(),
710        "**/*.tiff".to_string(),
711        "**/*.tif".to_string(),
712        "**/*.svg".to_string(),
713        "**/*.webp".to_string(),
714        "**/*.ico".to_string(),
715        "**/*.mp4".to_string(),
716        "**/*.avi".to_string(),
717        "**/*.mov".to_string(),
718        "**/*.wmv".to_string(),
719        "**/*.flv".to_string(),
720        "**/*.webm".to_string(),
721        "**/*.mkv".to_string(),
722        "**/*.mp3".to_string(),
723        "**/*.wav".to_string(),
724        "**/*.flac".to_string(),
725        "**/*.aac".to_string(),
726        "**/*.ogg".to_string(),
727        "**/*.wma".to_string(),
728        
729        // Font files
730        "**/*.woff".to_string(),
731        "**/*.woff2".to_string(),
732        "**/*.ttf".to_string(),
733        "**/*.otf".to_string(),
734        "**/*.eot".to_string(),
735        
736        // Compiled/binary files
737        "**/*.exe".to_string(),
738        "**/*.dll".to_string(),
739        "**/*.so".to_string(),
740        "**/*.dylib".to_string(),
741        "**/*.a".to_string(),
742        "**/*.lib".to_string(),
743        "**/*.o".to_string(),
744        "**/*.obj".to_string(),
745        "**/*.pyc".to_string(),
746        "**/*.pyo".to_string(),
747        "**/*.class".to_string(),
748        "**/*.elc".to_string(),
749        "**/*.beam".to_string(),
750    ];
751
752    // Framework-specific exclusions
753    for framework in &info.frameworks {
754        match framework {
755            Framework::NextJS => {
756                patterns.extend(vec![
757                    "**/.next/**".to_string(),
758                    "**/next-env.d.ts".to_string(),
759                ]);
760            }
761            Framework::Django => {
762                patterns.extend(vec![
763                    "**/migrations/**".to_string(),
764                    "**/staticfiles/**".to_string(),
765                    "**/media/**".to_string(),
766                ]);
767            }
768            Framework::Rails => {
769                patterns.extend(vec![
770                    "**/log/**".to_string(),
771                    "**/tmp/**".to_string(),
772                    "**/storage/**".to_string(),
773                    "**/public/assets/**".to_string(),
774                ]);
775            }
776            _ => {}
777        }
778    }
779
780    // Language-specific exclusions
781    for language in &info.languages {
782        match language {
783            Language::Java => {
784                patterns.extend(vec![
785                    "**/target/**".to_string(),
786                    "**/*.class".to_string(),
787                    "**/bin/**".to_string(),
788                ]);
789            }
790            Language::CSharp => {
791                patterns.extend(vec![
792                    "**/bin/**".to_string(),
793                    "**/obj/**".to_string(),
794                    "**/packages/**".to_string(),
795                ]);
796            }
797            Language::Go => {
798                patterns.extend(vec![
799                    "**/vendor/**".to_string(),
800                    "**/*.exe".to_string(),
801                    "**/*.test".to_string(),
802                ]);
803            }
804            Language::PHP => {
805                patterns.extend(vec![
806                    "**/vendor/**".to_string(),
807                    "**/storage/**".to_string(),
808                    "**/bootstrap/cache/**".to_string(),
809                ]);
810            }
811            _ => {}
812        }
813    }
814
815    patterns.sort();
816    patterns.dedup();
817    patterns
818}