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