syncable_cli/analyzer/frameworks/
javascript.rs

1use super::{LanguageFrameworkDetector, TechnologyRule, FrameworkDetectionUtils};
2use crate::analyzer::{DetectedTechnology, DetectedLanguage, TechnologyCategory, LibraryType};
3use crate::error::Result;
4use std::fs;
5use std::path::Path;
6
7pub struct JavaScriptFrameworkDetector;
8
9impl LanguageFrameworkDetector for JavaScriptFrameworkDetector {
10    fn detect_frameworks(&self, language: &DetectedLanguage) -> Result<Vec<DetectedTechnology>> {
11        let rules = get_js_technology_rules();
12        
13        // New: Enhanced detection using file-based approach first
14        let mut technologies = detect_frameworks_from_files(language, &rules)?;
15        
16        // Combine main and dev dependencies for comprehensive detection
17        let all_deps: Vec<String> = language.main_dependencies.iter()
18            .chain(language.dev_dependencies.iter())
19            .cloned()
20            .collect();
21        
22        // Enhanced detection: analyze actual source files for usage patterns
23        if let Some(enhanced_techs) = detect_technologies_from_source_files(language, &rules) {
24            // Merge with file-based detection, preferring higher confidence scores
25            for enhanced_tech in enhanced_techs {
26                if let Some(existing) = technologies.iter_mut().find(|t| t.name == enhanced_tech.name) {
27                    // Use higher confidence between file-based and source file analysis
28                    if enhanced_tech.confidence > existing.confidence {
29                        existing.confidence = enhanced_tech.confidence;
30                    }
31                } else {
32                    // Add new technology found in source files
33                    technologies.push(enhanced_tech);
34                }
35            }
36        }
37        
38        // Fallback to dependency-based detection
39        let dependency_based_techs = FrameworkDetectionUtils::detect_technologies_by_dependencies(
40            &rules, &all_deps, language.confidence
41        );
42        
43        // Merge dependency-based detections with higher confidence scores
44        for dep_tech in dependency_based_techs {
45            if let Some(existing) = technologies.iter_mut().find(|t| t.name == dep_tech.name) {
46                // Use higher confidence between file-based and dependency-based detection
47                if dep_tech.confidence > existing.confidence {
48                    existing.confidence = dep_tech.confidence;
49                }
50            } else {
51                // Add new technology found through dependencies
52                technologies.push(dep_tech);
53            }
54        }
55        
56        Ok(technologies)
57    }
58    
59    fn supported_languages(&self) -> Vec<&'static str> {
60        vec!["JavaScript", "TypeScript", "JavaScript/TypeScript"]
61    }
62}
63
64/// New: Enhanced detection that analyzes project files for framework indicators
65fn detect_frameworks_from_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Result<Vec<DetectedTechnology>> {
66    let mut detected = Vec::new();
67    
68    // Check for configuration files first (highest priority)
69    if let Some(config_detections) = detect_by_config_files(language, rules) {
70        detected.extend(config_detections);
71    }
72    
73    // If no config-based detections, check project structure (medium priority)
74    if detected.is_empty() {
75        if let Some(structure_detections) = detect_by_project_structure(language, rules) {
76            detected.extend(structure_detections);
77        }
78    }
79    
80    // Check source code patterns (lower priority)
81    if let Some(source_detections) = detect_by_source_patterns(language, rules) {
82        // Merge with existing detections, preferring higher confidence
83        for source_tech in source_detections {
84            if let Some(existing_tech) = detected.iter_mut().find(|t| t.name == source_tech.name) {
85                if source_tech.confidence > existing_tech.confidence {
86                    existing_tech.confidence = source_tech.confidence;
87                }
88            } else {
89                detected.push(source_tech);
90            }
91        }
92    }
93    
94    Ok(detected)
95}
96
97/// New: Detect frameworks by checking for framework-specific configuration files
98fn detect_by_config_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
99    let mut detected = Vec::new();
100    
101    // Check each file in the project for config files
102    for file_path in &language.files {
103        if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
104            // Check for Expo config files
105            if file_name == "app.json" || file_name == "app.config.js" || file_name == "app.config.ts" {
106                // For app.config files, we need to check the content to distinguish between Expo and TanStack Start
107                // But for testing purposes, we'll make assumptions based on file names and dependencies
108                if file_name == "app.config.js" || file_name == "app.config.ts" {
109                    // Check if we have Expo dependencies
110                    let has_expo_deps = language.main_dependencies.iter().any(|dep| dep == "expo" || dep == "react-native");
111                    let has_tanstack_deps = language.main_dependencies.iter().any(|dep| dep.contains("tanstack") || dep.contains("vinxi"));
112                    
113                    if has_expo_deps && !has_tanstack_deps {
114                        if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
115                            detected.push(DetectedTechnology {
116                                name: expo_rule.name.clone(),
117                                version: None,
118                                category: expo_rule.category.clone(),
119                                confidence: 1.0, // High confidence from config file with Expo content
120                                requires: expo_rule.requires.clone(),
121                                conflicts_with: expo_rule.conflicts_with.clone(),
122                                is_primary: expo_rule.is_primary_indicator,
123                                file_indicators: expo_rule.file_indicators.clone(),
124                            });
125                        }
126                    } else if has_tanstack_deps && !has_expo_deps {
127                        if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
128                            detected.push(DetectedTechnology {
129                                name: tanstack_rule.name.clone(),
130                                version: None,
131                                category: tanstack_rule.category.clone(),
132                                confidence: 1.0, // High confidence from config file with TanStack content
133                                requires: tanstack_rule.requires.clone(),
134                                conflicts_with: tanstack_rule.conflicts_with.clone(),
135                                is_primary: tanstack_rule.is_primary_indicator,
136                                file_indicators: tanstack_rule.file_indicators.clone(),
137                            });
138                        }
139                    }
140                    // If we can't determine, we'll skip for now
141                } else {
142                    // For app.json, we can assume it's Expo
143                    if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
144                        detected.push(DetectedTechnology {
145                            name: expo_rule.name.clone(),
146                            version: None,
147                            category: expo_rule.category.clone(),
148                            confidence: 1.0, // High confidence from config file
149                            requires: expo_rule.requires.clone(),
150                            conflicts_with: expo_rule.conflicts_with.clone(),
151                            is_primary: expo_rule.is_primary_indicator,
152                            file_indicators: expo_rule.file_indicators.clone(),
153                        });
154                    }
155                }
156            }
157            // Check for Next.js config files
158            else if file_name == "next.config.js" || file_name == "next.config.ts" {
159                if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
160                    detected.push(DetectedTechnology {
161                        name: nextjs_rule.name.clone(),
162                        version: None,
163                        category: nextjs_rule.category.clone(),
164                        confidence: 1.0, // High confidence from config file
165                        requires: nextjs_rule.requires.clone(),
166                        conflicts_with: nextjs_rule.conflicts_with.clone(),
167                        is_primary: nextjs_rule.is_primary_indicator,
168                        file_indicators: nextjs_rule.file_indicators.clone(),
169                    });
170                }
171            }
172            // Check for React Native config files
173            else if file_name == "react-native.config.js" {
174                if let Some(rn_rule) = rules.iter().find(|r| r.name == "React Native") {
175                    detected.push(DetectedTechnology {
176                        name: rn_rule.name.clone(),
177                        version: None,
178                        category: rn_rule.category.clone(),
179                        confidence: 1.0, // High confidence from config file
180                        requires: rn_rule.requires.clone(),
181                        conflicts_with: rn_rule.conflicts_with.clone(),
182                        is_primary: rn_rule.is_primary_indicator,
183                        file_indicators: rn_rule.file_indicators.clone(),
184                    });
185                }
186            }
187            // Check for Encore config files
188            else if file_name == "encore.app" || file_name == "encore.service.ts" || file_name == "encore.service.js" {
189                if let Some(encore_rule) = rules.iter().find(|r| r.name == "Encore") {
190                    detected.push(DetectedTechnology {
191                        name: encore_rule.name.clone(),
192                        version: None,
193                        category: encore_rule.category.clone(),
194                        confidence: 1.0, // High confidence from config file
195                        requires: encore_rule.requires.clone(),
196                        conflicts_with: encore_rule.conflicts_with.clone(),
197                        is_primary: encore_rule.is_primary_indicator,
198                        file_indicators: encore_rule.file_indicators.clone(),
199                    });
200                }
201            }
202        }
203    }
204    
205    if detected.is_empty() {
206        None
207    } else {
208        Some(detected)
209    }
210}
211
212/// New: Detect frameworks by analyzing project structure
213fn detect_by_project_structure(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
214    let mut detected = Vec::new();
215    let mut has_android_dir = false;
216    let mut has_ios_dir = false;
217    let mut has_pages_dir = false;
218    let mut has_app_dir = false;
219    let mut has_app_routes_dir = false;
220    let mut has_encore_app_file = false;
221    let mut has_encore_service_files = false;
222    let mut has_app_json = false;
223    let mut has_app_js_ts = false;
224    
225    // Check project directories
226    for file_path in &language.files {
227        if let Some(parent) = file_path.parent() {
228            let path_str = parent.to_string_lossy();
229            let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
230            
231            // Check for React Native structure
232            if path_str.contains("android") {
233                has_android_dir = true;
234            } else if path_str.contains("ios") {
235                has_ios_dir = true;
236            }
237            // Check for Next.js structure
238            else if path_str.contains("pages") {
239                has_pages_dir = true;
240            } else if path_str.contains("app") && !path_str.contains("app.config") && !path_str.contains("encore.app") {
241                has_app_dir = true;
242            }
243            // Check for TanStack Start structure
244            else if path_str.contains("app/routes") {
245                has_app_routes_dir = true;
246            }
247            // Check for Encore structure
248            else if file_name == "encore.app" {
249                has_encore_app_file = true;
250            } else if file_name.contains("encore.service.") {
251                has_encore_service_files = true;
252            }
253            // Check for Expo files
254            else if file_name == "app.json" {
255                has_app_json = true;
256            } else if file_name == "App.js" || file_name == "App.tsx" {
257                has_app_js_ts = true;
258            }
259        }
260    }
261    
262    // Check if we have Expo dependencies
263    let has_expo_deps = language.main_dependencies.iter().any(|dep| dep == "expo" || dep == "react-native");
264    
265    // Determine frameworks based on structure
266    if has_encore_app_file || has_encore_service_files {
267        // Likely Encore
268        if let Some(encore_rule) = rules.iter().find(|r| r.name == "Encore") {
269            detected.push(DetectedTechnology {
270                name: encore_rule.name.clone(),
271                version: None,
272                category: encore_rule.category.clone(),
273                confidence: 1.0, // High confidence from structure
274                requires: encore_rule.requires.clone(),
275                conflicts_with: encore_rule.conflicts_with.clone(),
276                is_primary: encore_rule.is_primary_indicator,
277                file_indicators: encore_rule.file_indicators.clone(),
278            });
279        }
280    } else if has_app_routes_dir {
281        // Likely TanStack Start
282        if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
283            detected.push(DetectedTechnology {
284                name: tanstack_rule.name.clone(),
285                version: None,
286                category: tanstack_rule.category.clone(),
287                confidence: 0.9, // Medium-high confidence from structure
288                requires: tanstack_rule.requires.clone(),
289                conflicts_with: tanstack_rule.conflicts_with.clone(),
290                is_primary: tanstack_rule.is_primary_indicator,
291                file_indicators: tanstack_rule.file_indicators.clone(),
292            });
293        }
294    } else if has_pages_dir || has_app_dir {
295        // Likely Next.js
296        if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
297            detected.push(DetectedTechnology {
298                name: nextjs_rule.name.clone(),
299                version: None,
300                category: nextjs_rule.category.clone(),
301                confidence: 0.9, // Medium-high confidence from structure
302                requires: nextjs_rule.requires.clone(),
303                conflicts_with: nextjs_rule.conflicts_with.clone(),
304                is_primary: nextjs_rule.is_primary_indicator,
305                file_indicators: nextjs_rule.file_indicators.clone(),
306            });
307        }
308    } else if (has_app_json || has_app_js_ts) && has_expo_deps {
309        // Likely Expo (don't require Android/iOS directories for simpler detection)
310        if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
311            detected.push(DetectedTechnology {
312                name: expo_rule.name.clone(),
313                version: None,
314                category: expo_rule.category.clone(),
315                confidence: 1.0, // High confidence from file structure and dependencies
316                requires: expo_rule.requires.clone(),
317                conflicts_with: expo_rule.conflicts_with.clone(),
318                is_primary: expo_rule.is_primary_indicator,
319                file_indicators: expo_rule.file_indicators.clone(),
320            });
321        }
322    } else if has_android_dir && has_ios_dir {
323        // Likely React Native
324        if let Some(rn_rule) = rules.iter().find(|r| r.name == "React Native") {
325            detected.push(DetectedTechnology {
326                name: rn_rule.name.clone(),
327                version: None,
328                category: rn_rule.category.clone(),
329                confidence: 0.9, // Medium-high confidence from structure
330                requires: rn_rule.requires.clone(),
331                conflicts_with: rn_rule.conflicts_with.clone(),
332                is_primary: rn_rule.is_primary_indicator,
333                file_indicators: rn_rule.file_indicators.clone(),
334            });
335        }
336    }
337    
338    if detected.is_empty() {
339        None
340    } else {
341        Some(detected)
342    }
343}
344
345/// New: Detect frameworks by analyzing source code patterns
346fn detect_by_source_patterns(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
347    let mut detected = Vec::new();
348    
349    // Analyze files for usage patterns
350    for file_path in &language.files {
351        if let Ok(content) = std::fs::read_to_string(file_path) {
352            // Check for Expo source patterns
353            if content.contains("expo") && (content.contains("from 'expo'") || content.contains("import {") && content.contains("registerRootComponent")) {
354                if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
355                    detected.push(DetectedTechnology {
356                        name: expo_rule.name.clone(),
357                        version: None,
358                        category: expo_rule.category.clone(),
359                        confidence: 0.8, // Higher confidence from more specific source patterns
360                        requires: expo_rule.requires.clone(),
361                        conflicts_with: expo_rule.conflicts_with.clone(),
362                        is_primary: expo_rule.is_primary_indicator,
363                        file_indicators: expo_rule.file_indicators.clone(),
364                    });
365                }
366            }
367            
368            // Check for Next.js source patterns
369            if content.contains("next/") {
370                if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
371                    detected.push(DetectedTechnology {
372                        name: nextjs_rule.name.clone(),
373                        version: None,
374                        category: nextjs_rule.category.clone(),
375                        confidence: 0.7, // Medium confidence from source patterns
376                        requires: nextjs_rule.requires.clone(),
377                        conflicts_with: nextjs_rule.conflicts_with.clone(),
378                        is_primary: nextjs_rule.is_primary_indicator,
379                        file_indicators: nextjs_rule.file_indicators.clone(),
380                    });
381                }
382            }
383            
384            // Check for TanStack Router patterns
385            if content.contains("@tanstack/react-router") && content.contains("createFileRoute") {
386                if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
387                    detected.push(DetectedTechnology {
388                        name: tanstack_rule.name.clone(),
389                        version: None,
390                        category: tanstack_rule.category.clone(),
391                        confidence: 0.7, // Medium confidence from source patterns
392                        requires: tanstack_rule.requires.clone(),
393                        conflicts_with: tanstack_rule.conflicts_with.clone(),
394                        is_primary: tanstack_rule.is_primary_indicator,
395                        file_indicators: tanstack_rule.file_indicators.clone(),
396                    });
397                }
398            }
399            
400            // Check for React Router patterns
401            if content.contains("react-router") && content.contains("BrowserRouter") {
402                if let Some(rr_rule) = rules.iter().find(|r| r.name == "React Router v7") {
403                    detected.push(DetectedTechnology {
404                        name: rr_rule.name.clone(),
405                        version: None,
406                        category: rr_rule.category.clone(),
407                        confidence: 0.7, // Medium confidence from source patterns
408                        requires: rr_rule.requires.clone(),
409                        conflicts_with: rr_rule.conflicts_with.clone(),
410                        is_primary: rr_rule.is_primary_indicator,
411                        file_indicators: rr_rule.file_indicators.clone(),
412                    });
413                }
414            }
415        }
416    }
417    
418    if detected.is_empty() {
419        None
420    } else {
421        Some(detected)
422    }
423}
424
425/// Enhanced detection that analyzes actual source files for technology usage patterns
426fn detect_technologies_from_source_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
427    
428    
429    let mut detected = Vec::new();
430    
431    // Analyze files for usage patterns
432    for file_path in &language.files {
433        if let Ok(content) = fs::read_to_string(file_path) {
434            // Analyze Drizzle ORM usage patterns
435            if let Some(drizzle_confidence) = analyze_drizzle_usage(&content, file_path) {
436                if let Some(drizzle_rule) = rules.iter().find(|r| r.name == "Drizzle ORM") {
437                    detected.push(DetectedTechnology {
438                        name: "Drizzle ORM".to_string(),
439                        version: None,
440                        category: TechnologyCategory::Database,
441                        confidence: drizzle_confidence,
442                        requires: vec![],
443                        conflicts_with: vec![],
444                        is_primary: false,
445                        file_indicators: drizzle_rule.file_indicators.clone(),
446                    });
447                }
448            }
449            
450            // Analyze Prisma usage patterns
451            if let Some(prisma_confidence) = analyze_prisma_usage(&content, file_path) {
452                if let Some(prisma_rule) = rules.iter().find(|r| r.name == "Prisma") {
453                    detected.push(DetectedTechnology {
454                        name: "Prisma".to_string(),
455                        version: None,
456                        category: TechnologyCategory::Database,
457                        confidence: prisma_confidence,
458                        requires: vec![],
459                        conflicts_with: vec![],
460                        is_primary: false,
461                        file_indicators: prisma_rule.file_indicators.clone(),
462                    });
463                }
464            }
465            
466            // Analyze Encore usage patterns
467            if let Some(encore_confidence) = analyze_encore_usage(&content, file_path) {
468                if let Some(encore_rule) = rules.iter().find(|r| r.name == "Encore") {
469                    detected.push(DetectedTechnology {
470                        name: "Encore".to_string(),
471                        version: None,
472                        category: TechnologyCategory::BackendFramework,
473                        confidence: encore_confidence,
474                        requires: vec![],
475                        conflicts_with: vec![],
476                        is_primary: true,
477                        file_indicators: encore_rule.file_indicators.clone(),
478                    });
479                }
480            }
481            
482            // Analyze Tanstack Start usage patterns
483            if let Some(tanstack_confidence) = analyze_tanstack_start_usage(&content, file_path) {
484                if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
485                    detected.push(DetectedTechnology {
486                        name: "Tanstack Start".to_string(),
487                        version: None,
488                        category: TechnologyCategory::MetaFramework,
489                        confidence: tanstack_confidence,
490                        requires: vec!["React".to_string()],
491                        conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
492                        is_primary: true,
493                        file_indicators: tanstack_rule.file_indicators.clone(),
494                    });
495                }
496            }
497        }
498    }
499    
500    if detected.is_empty() {
501        None
502    } else {
503        Some(detected)
504    }
505}
506
507/// Analyzes Drizzle ORM usage patterns in source files
508fn analyze_drizzle_usage(content: &str, file_path: &Path) -> Option<f32> {
509    let file_name = file_path.file_name()?.to_string_lossy();
510    let mut confidence: f32 = 0.0;
511    
512    // High confidence indicators
513    if content.contains("drizzle-orm") {
514        confidence += 0.3;
515    }
516    
517    // Schema file patterns (very high confidence)
518    if file_name.contains("schema") || file_name.contains("db.ts") || file_name.contains("database") {
519        if content.contains("pgTable") || content.contains("mysqlTable") || content.contains("sqliteTable") {
520            confidence += 0.4;
521        }
522        if content.contains("pgEnum") || content.contains("relations") {
523            confidence += 0.3;
524        }
525    }
526    
527    // Drizzle-specific imports
528    if content.contains("from 'drizzle-orm/pg-core'") || 
529       content.contains("from 'drizzle-orm/mysql-core'") ||
530       content.contains("from 'drizzle-orm/sqlite-core'") {
531        confidence += 0.3;
532    }
533    
534    // Drizzle query patterns
535    if content.contains("db.select()") || content.contains("db.insert()") || 
536       content.contains("db.update()") || content.contains("db.delete()") {
537        confidence += 0.2;
538    }
539    
540    // Configuration patterns
541    if content.contains("drizzle(") && (content.contains("connectionString") || content.contains("postgres(")) {
542        confidence += 0.2;
543    }
544    
545    // Migration patterns
546    if content.contains("drizzle.config") || file_name.contains("migrate") {
547        confidence += 0.2;
548    }
549    
550    // Prepared statements
551    if content.contains(".prepare()") && content.contains("drizzle") {
552        confidence += 0.1;
553    }
554    
555    if confidence > 0.0 {
556        Some(confidence.min(1.0_f32))
557    } else {
558        None
559    }
560}
561
562/// Analyzes Prisma usage patterns in source files
563fn analyze_prisma_usage(content: &str, file_path: &Path) -> Option<f32> {
564    let file_name = file_path.file_name()?.to_string_lossy();
565    let mut confidence: f32 = 0.0;
566    let mut has_prisma_import = false;
567    
568    // Only detect Prisma if there are actual Prisma-specific imports
569    if content.contains("@prisma/client") || content.contains("from '@prisma/client'") {
570        confidence += 0.4;
571        has_prisma_import = true;
572    }
573    
574    // Prisma schema files (very specific)
575    if file_name == "schema.prisma" {
576        if content.contains("model ") || content.contains("generator ") || content.contains("datasource ") {
577            confidence += 0.6;
578            has_prisma_import = true;
579        }
580    }
581    
582    // Only check for client usage if we have confirmed Prisma imports
583    if has_prisma_import {
584        // Prisma client instantiation (very specific)
585        if content.contains("new PrismaClient") || content.contains("PrismaClient()") {
586            confidence += 0.3;
587        }
588        
589        // Prisma-specific query patterns (only if we know it's Prisma)
590        if content.contains("prisma.") && (
591            content.contains(".findUnique(") || 
592            content.contains(".findFirst(") || 
593            content.contains(".upsert(") ||
594            content.contains(".$connect()") ||
595            content.contains(".$disconnect()")
596        ) {
597            confidence += 0.2;
598        }
599    }
600    
601    // Only return confidence if we have actual Prisma indicators
602    if confidence > 0.0 && has_prisma_import {
603        Some(confidence.min(1.0_f32))
604    } else {
605        None
606    }
607}
608
609/// Analyzes Encore usage patterns in source files
610fn analyze_encore_usage(content: &str, file_path: &Path) -> Option<f32> {
611    let file_name = file_path.file_name()?.to_string_lossy();
612    let mut confidence: f32 = 0.0;
613    
614    // Skip generated files (like Encore client code)
615    if content.contains("// Code generated by the Encore") || content.contains("DO NOT EDIT") {
616        return None;
617    }
618    
619    // Skip client-only files (generated or consumption only)
620    if file_name.contains("client.ts") || file_name.contains("client.js") {
621        return None;
622    }
623    
624    // Only detect Encore when there are actual service development patterns
625    let mut has_service_patterns = false;
626    
627    // Service definition files (high confidence for actual Encore development)
628    if file_name.contains("encore.service") || file_name.contains("service.ts") {
629        confidence += 0.4;
630        has_service_patterns = true;
631    }
632    
633    // API endpoint definitions (indicates actual Encore service development)
634    if content.contains("encore.dev/api") && (content.contains("export") || content.contains("api.")) {
635        confidence += 0.4;
636        has_service_patterns = true;
637    }
638    
639    // Database service patterns (actual Encore service code)
640    if content.contains("SQLDatabase") && content.contains("encore.dev") {
641        confidence += 0.3;
642        has_service_patterns = true;
643    }
644    
645    // Secret configuration (actual Encore service code)
646    if content.contains("secret(") && content.contains("encore.dev/config") {
647        confidence += 0.3;
648        has_service_patterns = true;
649    }
650    
651    // PubSub service patterns (actual Encore service code)
652    if content.contains("Topic") && content.contains("encore.dev/pubsub") {
653        confidence += 0.3;
654        has_service_patterns = true;
655    }
656    
657    // Cron job patterns (actual Encore service code)
658    if content.contains("cron") && content.contains("encore.dev") {
659        confidence += 0.2;
660        has_service_patterns = true;
661    }
662    
663    // Only return confidence if we have actual service development patterns
664    if confidence > 0.0 && has_service_patterns {
665        Some(confidence.min(1.0_f32))
666    } else {
667        None
668    }
669}
670
671/// Analyzes Tanstack Start usage patterns in source files
672fn analyze_tanstack_start_usage(content: &str, file_path: &Path) -> Option<f32> {
673    let file_name = file_path.file_name()?.to_string_lossy();
674    let mut confidence: f32 = 0.0;
675    let mut has_start_patterns = false;
676    
677    // Configuration files (high confidence)
678    if file_name == "app.config.ts" || file_name == "app.config.js" {
679        if content.contains("@tanstack/react-start") || content.contains("tanstack") {
680            confidence += 0.5;
681            has_start_patterns = true;
682        }
683    }
684    
685    // Router configuration patterns (very high confidence)
686    if file_name.contains("router.") && (file_name.ends_with(".ts") || file_name.ends_with(".tsx")) {
687        if content.contains("createRouter") && content.contains("@tanstack/react-router") {
688            confidence += 0.4;
689            has_start_patterns = true;
690        }
691        if content.contains("routeTree") {
692            confidence += 0.2;
693            has_start_patterns = true;
694        }
695    }
696    
697    // Server entry point patterns
698    if file_name == "ssr.tsx" || file_name == "ssr.ts" {
699        if content.contains("createStartHandler") || content.contains("@tanstack/react-start/server") {
700            confidence += 0.5;
701            has_start_patterns = true;
702        }
703    }
704    
705    // Client entry point patterns
706    if file_name == "client.tsx" || file_name == "client.ts" {
707        if content.contains("StartClient") && content.contains("@tanstack/react-start") {
708            confidence += 0.5;
709            has_start_patterns = true;
710        }
711        if content.contains("hydrateRoot") && content.contains("createRouter") {
712            confidence += 0.3;
713            has_start_patterns = true;
714        }
715    }
716    
717    // Root route patterns (in app/routes/__root.tsx)
718    if file_name == "__root.tsx" || file_name == "__root.ts" {
719        if content.contains("createRootRoute") && content.contains("@tanstack/react-router") {
720            confidence += 0.4;
721            has_start_patterns = true;
722        }
723        if content.contains("HeadContent") && content.contains("Scripts") {
724            confidence += 0.3;
725            has_start_patterns = true;
726        }
727    }
728    
729    // Route files with createFileRoute
730    if file_path.to_string_lossy().contains("routes/") {
731        if content.contains("createFileRoute") && content.contains("@tanstack/react-router") {
732            confidence += 0.3;
733            has_start_patterns = true;
734        }
735    }
736    
737    // Server functions (key Tanstack Start feature)
738    if content.contains("createServerFn") && content.contains("@tanstack/react-start") {
739        confidence += 0.4;
740        has_start_patterns = true;
741    }
742    
743    // Import patterns specific to Tanstack Start
744    if content.contains("from '@tanstack/react-start'") {
745        confidence += 0.3;
746        has_start_patterns = true;
747    }
748    
749    // Vinxi configuration patterns
750    if file_name == "vinxi.config.ts" || file_name == "vinxi.config.js" {
751        confidence += 0.2;
752        has_start_patterns = true;
753    }
754    
755    // Only return confidence if we have actual Tanstack Start patterns
756    if confidence > 0.0 && has_start_patterns {
757        Some(confidence.min(1.0_f32))
758    } else {
759        None
760    }
761}
762
763/// JavaScript/TypeScript technology detection rules with proper classification
764fn get_js_technology_rules() -> Vec<TechnologyRule> {
765    vec![
766        // META-FRAMEWORKS (Mutually Exclusive)
767        TechnologyRule {
768            name: "Next.js".to_string(),
769            category: TechnologyCategory::MetaFramework,
770            confidence: 0.95,
771            dependency_patterns: vec!["next".to_string()],
772            requires: vec!["React".to_string()],
773            conflicts_with: vec!["Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Expo".to_string()],
774            is_primary_indicator: true,
775            alternative_names: vec!["nextjs".to_string()],
776            file_indicators: vec!["next.config.js".to_string(), "next.config.ts".to_string(), "pages/".to_string(), "app/".to_string()],
777        },
778        TechnologyRule {
779            name: "Tanstack Start".to_string(),
780            category: TechnologyCategory::MetaFramework,
781            confidence: 0.95,
782            dependency_patterns: vec!["@tanstack/react-start".to_string()],
783            requires: vec!["React".to_string()],
784            conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
785            is_primary_indicator: true,
786            alternative_names: vec!["tanstack-start".to_string(), "TanStack Start".to_string()],
787            file_indicators: vec!["app.config.ts".to_string(), "app.config.js".to_string(), "app/routes/".to_string(), "vite.config.ts".to_string()],
788        },
789        TechnologyRule {
790            name: "React Router v7".to_string(),
791            category: TechnologyCategory::MetaFramework,
792            confidence: 0.95,
793            dependency_patterns: vec!["react-router".to_string(), "react-dom".to_string(), "react-router-dom".to_string()],
794            requires: vec!["React".to_string()],
795            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "React Native".to_string(), "Expo".to_string()],
796            is_primary_indicator: true,
797            alternative_names: vec!["remix".to_string(), "react-router".to_string()],
798            file_indicators: vec![],
799        },
800        TechnologyRule {
801            name: "SvelteKit".to_string(),
802            category: TechnologyCategory::MetaFramework,
803            confidence: 0.95,
804            dependency_patterns: vec!["@sveltejs/kit".to_string()],
805            requires: vec!["Svelte".to_string()],
806            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "Nuxt.js".to_string()],
807            is_primary_indicator: true,
808            alternative_names: vec!["svelte-kit".to_string()],
809            file_indicators: vec![],
810        },
811        TechnologyRule {
812            name: "Nuxt.js".to_string(),
813            category: TechnologyCategory::MetaFramework,
814            confidence: 0.95,
815            dependency_patterns: vec!["nuxt".to_string(), "@nuxt/core".to_string()],
816            requires: vec!["Vue.js".to_string()],
817            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
818            is_primary_indicator: true,
819            alternative_names: vec!["nuxtjs".to_string()],
820            file_indicators: vec![],
821        },
822        TechnologyRule {
823            name: "Astro".to_string(),
824            category: TechnologyCategory::MetaFramework,
825            confidence: 0.95,
826            dependency_patterns: vec!["astro".to_string()],
827            requires: vec![],
828            conflicts_with: vec![],
829            is_primary_indicator: true,
830            alternative_names: vec![],
831            file_indicators: vec![],
832        },
833        TechnologyRule {
834            name: "SolidStart".to_string(),
835            category: TechnologyCategory::MetaFramework,
836            confidence: 0.95,
837            dependency_patterns: vec!["solid-start".to_string()],
838            requires: vec!["SolidJS".to_string()],
839            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
840            is_primary_indicator: true,
841            alternative_names: vec![],
842            file_indicators: vec![],
843        },
844        
845        // MOBILE FRAMEWORKS (React Native/Expo)
846        TechnologyRule {
847            name: "React Native".to_string(),
848            category: TechnologyCategory::FrontendFramework,
849            confidence: 0.95,
850            dependency_patterns: vec!["react-native".to_string()],
851            requires: vec!["React".to_string()],
852            conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
853            is_primary_indicator: true,
854            alternative_names: vec!["reactnative".to_string()],
855            file_indicators: vec!["react-native.config.js".to_string(), "android/".to_string(), "ios/".to_string()],
856        },
857        TechnologyRule {
858            name: "Expo".to_string(),
859            category: TechnologyCategory::MetaFramework,
860            confidence: 1.0,
861            dependency_patterns: vec!["expo".to_string(), "expo-router".to_string(), "@expo/vector-icons".to_string()],
862            requires: vec!["React Native".to_string()],
863            conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
864            is_primary_indicator: true,
865            alternative_names: vec![],
866            file_indicators: vec!["app.json".to_string(), "app.config.js".to_string(), "app.config.ts".to_string()],
867        },
868        
869        // FRONTEND FRAMEWORKS (Provide structure)
870        TechnologyRule {
871            name: "Angular".to_string(),
872            category: TechnologyCategory::FrontendFramework,
873            confidence: 0.90,
874            dependency_patterns: vec!["@angular/core".to_string()],
875            requires: vec![],
876            conflicts_with: vec![],
877            is_primary_indicator: true,
878            alternative_names: vec!["angular".to_string()],
879            file_indicators: vec![],
880        },
881        TechnologyRule {
882            name: "Svelte".to_string(),
883            category: TechnologyCategory::FrontendFramework,
884            confidence: 0.95,
885            dependency_patterns: vec!["svelte".to_string()],
886            requires: vec![],
887            conflicts_with: vec![],
888            is_primary_indicator: false, // SvelteKit would be primary
889            alternative_names: vec![],
890            file_indicators: vec![],
891        },
892        
893        // UI LIBRARIES (Not frameworks!)
894        TechnologyRule {
895            name: "React".to_string(),
896            category: TechnologyCategory::Library(LibraryType::UI),
897            confidence: 0.90,
898            dependency_patterns: vec!["react".to_string()],
899            requires: vec![],
900            conflicts_with: vec![],
901            is_primary_indicator: false, // Meta-frameworks using React would be primary
902            alternative_names: vec!["reactjs".to_string()],
903            file_indicators: vec![],
904        },
905        TechnologyRule {
906            name: "Vue.js".to_string(),
907            category: TechnologyCategory::Library(LibraryType::UI),
908            confidence: 0.90,
909            dependency_patterns: vec!["vue".to_string()],
910            requires: vec![],
911            conflicts_with: vec![],
912            is_primary_indicator: false,
913            alternative_names: vec!["vuejs".to_string()],
914            file_indicators: vec![],
915        },
916        TechnologyRule {
917            name: "SolidJS".to_string(),
918            category: TechnologyCategory::Library(LibraryType::UI),
919            confidence: 0.95,
920            dependency_patterns: vec!["solid-js".to_string()],
921            requires: vec![],
922            conflicts_with: vec![],
923            is_primary_indicator: false,
924            alternative_names: vec!["solid".to_string()],
925            file_indicators: vec![],
926        },
927        TechnologyRule {
928            name: "HTMX".to_string(),
929            category: TechnologyCategory::Library(LibraryType::UI),
930            confidence: 0.95,
931            dependency_patterns: vec!["htmx.org".to_string()],
932            requires: vec![],
933            conflicts_with: vec![],
934            is_primary_indicator: false,
935            alternative_names: vec!["htmx".to_string()],
936            file_indicators: vec![],
937        },
938        
939        // BACKEND FRAMEWORKS
940        TechnologyRule {
941            name: "Express.js".to_string(),
942            category: TechnologyCategory::BackendFramework,
943            confidence: 0.95,
944            dependency_patterns: vec!["express".to_string()],
945            requires: vec![],
946            conflicts_with: vec![],
947            is_primary_indicator: true,
948            alternative_names: vec!["express".to_string()],
949            file_indicators: vec![],
950        },
951        TechnologyRule {
952            name: "Fastify".to_string(),
953            category: TechnologyCategory::BackendFramework,
954            confidence: 0.95,
955            dependency_patterns: vec!["fastify".to_string()],
956            requires: vec![],
957            conflicts_with: vec![],
958            is_primary_indicator: true,
959            alternative_names: vec![],
960            file_indicators: vec![],
961        },
962        TechnologyRule {
963            name: "Nest.js".to_string(),
964            category: TechnologyCategory::BackendFramework,
965            confidence: 0.95,
966            dependency_patterns: vec!["@nestjs/core".to_string()],
967            requires: vec![],
968            conflicts_with: vec![],
969            is_primary_indicator: true,
970            alternative_names: vec!["nestjs".to_string()],
971            file_indicators: vec![],
972        },
973        TechnologyRule {
974            name: "Hono".to_string(),
975            category: TechnologyCategory::BackendFramework,
976            confidence: 0.95,
977            dependency_patterns: vec!["hono".to_string()],
978            requires: vec![],
979            conflicts_with: vec![],
980            is_primary_indicator: true,
981            alternative_names: vec![],
982            file_indicators: vec![],
983        },
984        TechnologyRule {
985            name: "Elysia".to_string(),
986            category: TechnologyCategory::BackendFramework,
987            confidence: 0.95,
988            dependency_patterns: vec!["elysia".to_string()],
989            requires: vec![],
990            conflicts_with: vec![],
991            is_primary_indicator: true,
992            alternative_names: vec![],
993            file_indicators: vec![],
994        },
995        TechnologyRule {
996            name: "Encore".to_string(),
997            category: TechnologyCategory::BackendFramework,
998            confidence: 0.95,
999            dependency_patterns: vec!["encore.dev".to_string(), "encore".to_string()],
1000            requires: vec![],
1001            conflicts_with: vec!["Next.js".to_string()],
1002            is_primary_indicator: true,
1003            alternative_names: vec!["encore-ts-starter".to_string()],
1004            file_indicators: vec!["encore.app".to_string(), "encore.service.ts".to_string(), "encore.service.js".to_string()],
1005        },
1006        
1007        // BUILD TOOLS (Not frameworks!)
1008        TechnologyRule {
1009            name: "Vite".to_string(),
1010            category: TechnologyCategory::BuildTool,
1011            confidence: 0.80,
1012            dependency_patterns: vec!["vite".to_string()],
1013            requires: vec![],
1014            conflicts_with: vec![],
1015            is_primary_indicator: false,
1016            alternative_names: vec![],
1017            file_indicators: vec![],
1018        },
1019        TechnologyRule {
1020            name: "Webpack".to_string(),
1021            category: TechnologyCategory::BuildTool,
1022            confidence: 0.80,
1023            dependency_patterns: vec!["webpack".to_string()],
1024            requires: vec![],
1025            conflicts_with: vec![],
1026            is_primary_indicator: false,
1027            alternative_names: vec![],
1028            file_indicators: vec![],
1029        },
1030        
1031        // DATABASE/ORM (Important for Docker/infrastructure setup, migrations, etc.)
1032        TechnologyRule {
1033            name: "Prisma".to_string(),
1034            category: TechnologyCategory::Database,
1035            confidence: 0.90,
1036            dependency_patterns: vec!["prisma".to_string(), "@prisma/client".to_string()],
1037            requires: vec![],
1038            conflicts_with: vec![],
1039            is_primary_indicator: false,
1040            alternative_names: vec![],
1041            file_indicators: vec![],
1042        },
1043        TechnologyRule {
1044            name: "Drizzle ORM".to_string(),
1045            category: TechnologyCategory::Database,
1046            confidence: 0.90,
1047            dependency_patterns: vec!["drizzle-orm".to_string(), "drizzle-kit".to_string()],
1048            requires: vec![],
1049            conflicts_with: vec![],
1050            is_primary_indicator: false,
1051            alternative_names: vec!["drizzle".to_string()],
1052            file_indicators: vec![],
1053        },
1054        TechnologyRule {
1055            name: "Sequelize".to_string(),
1056            category: TechnologyCategory::Database,
1057            confidence: 0.90,
1058            dependency_patterns: vec!["sequelize".to_string()],
1059            requires: vec![],
1060            conflicts_with: vec![],
1061            is_primary_indicator: false,
1062            alternative_names: vec![],
1063            file_indicators: vec![],
1064        },
1065        TechnologyRule {
1066            name: "TypeORM".to_string(),
1067            category: TechnologyCategory::Database,
1068            confidence: 0.90,
1069            dependency_patterns: vec!["typeorm".to_string()],
1070            requires: vec![],
1071            conflicts_with: vec![],
1072            is_primary_indicator: false,
1073            alternative_names: vec![],
1074            file_indicators: vec![],
1075        },
1076        TechnologyRule {
1077            name: "MikroORM".to_string(),
1078            category: TechnologyCategory::Database,
1079            confidence: 0.90,
1080            dependency_patterns: vec!["@mikro-orm/core".to_string(), "@mikro-orm/postgresql".to_string(), "@mikro-orm/mysql".to_string(), "@mikro-orm/sqlite".to_string(), "@mikro-orm/mongodb".to_string()],
1081            requires: vec![],
1082            conflicts_with: vec![],
1083            is_primary_indicator: false,
1084            alternative_names: vec!["mikro-orm".to_string()],
1085            file_indicators: vec![],
1086        },
1087        TechnologyRule {
1088            name: "Mongoose".to_string(),
1089            category: TechnologyCategory::Database,
1090            confidence: 0.95,
1091            dependency_patterns: vec!["mongoose".to_string()],
1092            requires: vec![],
1093            conflicts_with: vec![],
1094            is_primary_indicator: false,
1095            alternative_names: vec![],
1096            file_indicators: vec![],
1097        },
1098        TechnologyRule {
1099            name: "Typegoose".to_string(),
1100            category: TechnologyCategory::Database,
1101            confidence: 0.90,
1102            dependency_patterns: vec!["@typegoose/typegoose".to_string()],
1103            requires: vec!["Mongoose".to_string()],
1104            conflicts_with: vec![],
1105            is_primary_indicator: false,
1106            alternative_names: vec![],
1107            file_indicators: vec![],
1108        },
1109        TechnologyRule {
1110            name: "Objection.js".to_string(),
1111            category: TechnologyCategory::Database,
1112            confidence: 0.90,
1113            dependency_patterns: vec!["objection".to_string()],
1114            requires: vec!["Knex.js".to_string()],
1115            conflicts_with: vec![],
1116            is_primary_indicator: false,
1117            alternative_names: vec!["objectionjs".to_string()],
1118            file_indicators: vec![],
1119        },
1120        TechnologyRule {
1121            name: "Bookshelf".to_string(),
1122            category: TechnologyCategory::Database,
1123            confidence: 0.85,
1124            dependency_patterns: vec!["bookshelf".to_string()],
1125            requires: vec!["Knex.js".to_string()],
1126            conflicts_with: vec![],
1127            is_primary_indicator: false,
1128            alternative_names: vec![],
1129            file_indicators: vec![],
1130        },
1131        TechnologyRule {
1132            name: "Waterline".to_string(),
1133            category: TechnologyCategory::Database,
1134            confidence: 0.85,
1135            dependency_patterns: vec!["waterline".to_string(), "sails-mysql".to_string(), "sails-postgresql".to_string(), "sails-disk".to_string()],
1136            requires: vec![],
1137            conflicts_with: vec![],
1138            is_primary_indicator: false,
1139            alternative_names: vec![],
1140            file_indicators: vec![],
1141        },
1142        TechnologyRule {
1143            name: "Knex.js".to_string(),
1144            category: TechnologyCategory::Database,
1145            confidence: 0.85,
1146            dependency_patterns: vec!["knex".to_string()],
1147            requires: vec![],
1148            conflicts_with: vec![],
1149            is_primary_indicator: false,
1150            alternative_names: vec!["knexjs".to_string()],
1151            file_indicators: vec![],
1152        },
1153        
1154        // RUNTIMES (Important for IaC - determines base images, package managers)
1155        TechnologyRule {
1156            name: "Node.js".to_string(),
1157            category: TechnologyCategory::Runtime,
1158            confidence: 0.90,
1159            dependency_patterns: vec!["node".to_string()], // This will need file-based detection
1160            requires: vec![],
1161            conflicts_with: vec![],
1162            is_primary_indicator: false,
1163            alternative_names: vec!["nodejs".to_string()],
1164            file_indicators: vec![],
1165        },
1166        TechnologyRule {
1167            name: "Bun".to_string(),
1168            category: TechnologyCategory::Runtime,
1169            confidence: 0.95,
1170            dependency_patterns: vec!["bun".to_string()], // Look for bun in devDependencies or bun.lockb file
1171            requires: vec![],
1172            conflicts_with: vec![],
1173            is_primary_indicator: false,
1174            alternative_names: vec![],
1175            file_indicators: vec![],
1176        },
1177        TechnologyRule {
1178            name: "Deno".to_string(),
1179            category: TechnologyCategory::Runtime,
1180            confidence: 0.95,
1181            dependency_patterns: vec!["@deno/core".to_string(), "deno".to_string()],
1182            requires: vec![],
1183            conflicts_with: vec![],
1184            is_primary_indicator: false,
1185            alternative_names: vec![],
1186            file_indicators: vec![],
1187        },
1188        TechnologyRule {
1189            name: "WinterJS".to_string(),
1190            category: TechnologyCategory::Runtime,
1191            confidence: 0.95,
1192            dependency_patterns: vec!["winterjs".to_string(), "winter-js".to_string()],
1193            requires: vec![],
1194            conflicts_with: vec![],
1195            is_primary_indicator: false,
1196            alternative_names: vec!["winter.js".to_string()],
1197            file_indicators: vec![],
1198        },
1199        TechnologyRule {
1200            name: "Cloudflare Workers".to_string(),
1201            category: TechnologyCategory::Runtime,
1202            confidence: 0.90,
1203            dependency_patterns: vec!["@cloudflare/workers-types".to_string(), "@cloudflare/vitest-pool-workers".to_string(), "wrangler".to_string()],
1204            requires: vec![],
1205            conflicts_with: vec![],
1206            is_primary_indicator: false,
1207            alternative_names: vec!["cloudflare-workers".to_string()],
1208            file_indicators: vec![],
1209        },
1210        TechnologyRule {
1211            name: "Vercel Edge Runtime".to_string(),
1212            category: TechnologyCategory::Runtime,
1213            confidence: 0.90,
1214            dependency_patterns: vec!["@vercel/edge-runtime".to_string(), "@edge-runtime/vm".to_string()],
1215            requires: vec![],
1216            conflicts_with: vec![],
1217            is_primary_indicator: false,
1218            alternative_names: vec!["vercel-edge".to_string()],
1219            file_indicators: vec![],
1220        },
1221        TechnologyRule {
1222            name: "Hermes".to_string(),
1223            category: TechnologyCategory::Runtime,
1224            confidence: 0.85,
1225            dependency_patterns: vec!["hermes-engine".to_string()],
1226            requires: vec!["React Native".to_string()],
1227            conflicts_with: vec![],
1228            is_primary_indicator: false,
1229            alternative_names: vec![],
1230            file_indicators: vec![],
1231        },
1232        TechnologyRule {
1233            name: "Electron".to_string(),
1234            category: TechnologyCategory::Runtime,
1235            confidence: 0.95,
1236            dependency_patterns: vec!["electron".to_string()],
1237            requires: vec![],
1238            conflicts_with: vec![],
1239            is_primary_indicator: false,
1240            alternative_names: vec![],
1241            file_indicators: vec![],
1242        },
1243        TechnologyRule {
1244            name: "Tauri".to_string(),
1245            category: TechnologyCategory::Runtime,
1246            confidence: 0.95,
1247            dependency_patterns: vec!["@tauri-apps/cli".to_string(), "@tauri-apps/api".to_string()],
1248            requires: vec![],
1249            conflicts_with: vec!["Electron".to_string()],
1250            is_primary_indicator: false,
1251            alternative_names: vec![],
1252            file_indicators: vec![],
1253        },
1254        TechnologyRule {
1255            name: "QuickJS".to_string(),
1256            category: TechnologyCategory::Runtime,
1257            confidence: 0.85,
1258            dependency_patterns: vec!["quickjs".to_string(), "quickjs-emscripten".to_string()],
1259            requires: vec![],
1260            conflicts_with: vec![],
1261            is_primary_indicator: false,
1262            alternative_names: vec![],
1263            file_indicators: vec![],
1264        },
1265        
1266        // TESTING (Keep minimal - only major frameworks that affect build process)
1267        TechnologyRule {
1268            name: "Jest".to_string(),
1269            category: TechnologyCategory::Testing,
1270            confidence: 0.85,
1271            dependency_patterns: vec!["jest".to_string()],
1272            requires: vec![],
1273            conflicts_with: vec![],
1274            is_primary_indicator: false,
1275            alternative_names: vec![],
1276            file_indicators: vec![],
1277        },
1278        TechnologyRule {
1279            name: "Vitest".to_string(),
1280            category: TechnologyCategory::Testing,
1281            confidence: 0.85,
1282            dependency_patterns: vec!["vitest".to_string()],
1283            requires: vec![],
1284            conflicts_with: vec![],
1285            is_primary_indicator: false,
1286            alternative_names: vec![],
1287            file_indicators: vec![],
1288        },
1289    ]
1290}