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