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        TechnologyRule {
817            name: "React Router v7".to_string(),
818            category: TechnologyCategory::MetaFramework,
819            confidence: 0.95,
820            dependency_patterns: vec!["react-router".to_string(), "react-dom".to_string(), "react-router-dom".to_string()],
821            requires: vec!["React".to_string()],
822            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()],
823            is_primary_indicator: true,
824            alternative_names: vec!["remix".to_string(), "react-router".to_string()],
825            file_indicators: vec![],
826        },
827        TechnologyRule {
828            name: "SvelteKit".to_string(),
829            category: TechnologyCategory::MetaFramework,
830            confidence: 0.95,
831            dependency_patterns: vec!["@sveltejs/kit".to_string()],
832            requires: vec!["Svelte".to_string()],
833            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "Nuxt.js".to_string()],
834            is_primary_indicator: true,
835            alternative_names: vec!["svelte-kit".to_string()],
836            file_indicators: vec![],
837        },
838        TechnologyRule {
839            name: "Nuxt.js".to_string(),
840            category: TechnologyCategory::MetaFramework,
841            confidence: 0.95,
842            dependency_patterns: vec!["nuxt".to_string(), "@nuxt/core".to_string()],
843            requires: vec!["Vue.js".to_string()],
844            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
845            is_primary_indicator: true,
846            alternative_names: vec!["nuxtjs".to_string()],
847            file_indicators: vec![],
848        },
849        TechnologyRule {
850            name: "Astro".to_string(),
851            category: TechnologyCategory::MetaFramework,
852            confidence: 0.95,
853            dependency_patterns: vec!["astro".to_string()],
854            requires: vec![],
855            conflicts_with: vec![],
856            is_primary_indicator: true,
857            alternative_names: vec![],
858            file_indicators: vec![],
859        },
860        TechnologyRule {
861            name: "SolidStart".to_string(),
862            category: TechnologyCategory::MetaFramework,
863            confidence: 0.95,
864            dependency_patterns: vec!["solid-start".to_string()],
865            requires: vec!["SolidJS".to_string()],
866            conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
867            is_primary_indicator: true,
868            alternative_names: vec![],
869            file_indicators: vec![],
870        },
871        
872        // MOBILE FRAMEWORKS (React Native/Expo)
873        TechnologyRule {
874            name: "React Native".to_string(),
875            category: TechnologyCategory::FrontendFramework,
876            confidence: 0.95,
877            dependency_patterns: vec!["react-native".to_string()],
878            requires: vec!["React".to_string()],
879            conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
880            is_primary_indicator: true,
881            alternative_names: vec!["reactnative".to_string()],
882            file_indicators: vec!["react-native.config.js".to_string(), "android/".to_string(), "ios/".to_string()],
883        },
884        TechnologyRule {
885            name: "Expo".to_string(),
886            category: TechnologyCategory::MetaFramework,
887            confidence: 1.0,
888            dependency_patterns: vec!["expo".to_string(), "expo-router".to_string(), "@expo/vector-icons".to_string()],
889            requires: vec!["React Native".to_string()],
890            conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
891            is_primary_indicator: true,
892            alternative_names: vec![],
893            file_indicators: vec!["app.json".to_string(), "app.config.js".to_string(), "app.config.ts".to_string()],
894        },
895        
896        // FRONTEND FRAMEWORKS (Provide structure)
897        TechnologyRule {
898            name: "Angular".to_string(),
899            category: TechnologyCategory::FrontendFramework,
900            confidence: 0.90,
901            dependency_patterns: vec!["@angular/core".to_string()],
902            requires: vec![],
903            conflicts_with: vec![],
904            is_primary_indicator: true,
905            alternative_names: vec!["angular".to_string()],
906            file_indicators: vec![],
907        },
908        TechnologyRule {
909            name: "Svelte".to_string(),
910            category: TechnologyCategory::FrontendFramework,
911            confidence: 0.95,
912            dependency_patterns: vec!["svelte".to_string()],
913            requires: vec![],
914            conflicts_with: vec![],
915            is_primary_indicator: false, // SvelteKit would be primary
916            alternative_names: vec![],
917            file_indicators: vec![],
918        },
919        
920        // UI LIBRARIES (Not frameworks!)
921        TechnologyRule {
922            name: "React".to_string(),
923            category: TechnologyCategory::Library(LibraryType::UI),
924            confidence: 0.90,
925            dependency_patterns: vec!["react".to_string()],
926            requires: vec![],
927            conflicts_with: vec![],
928            is_primary_indicator: false, // Meta-frameworks using React would be primary
929            alternative_names: vec!["reactjs".to_string()],
930            file_indicators: vec![],
931        },
932        TechnologyRule {
933            name: "Vue.js".to_string(),
934            category: TechnologyCategory::Library(LibraryType::UI),
935            confidence: 0.90,
936            dependency_patterns: vec!["vue".to_string()],
937            requires: vec![],
938            conflicts_with: vec![],
939            is_primary_indicator: false,
940            alternative_names: vec!["vuejs".to_string()],
941            file_indicators: vec![],
942        },
943        TechnologyRule {
944            name: "SolidJS".to_string(),
945            category: TechnologyCategory::Library(LibraryType::UI),
946            confidence: 0.95,
947            dependency_patterns: vec!["solid-js".to_string()],
948            requires: vec![],
949            conflicts_with: vec![],
950            is_primary_indicator: false,
951            alternative_names: vec!["solid".to_string()],
952            file_indicators: vec![],
953        },
954        TechnologyRule {
955            name: "HTMX".to_string(),
956            category: TechnologyCategory::Library(LibraryType::UI),
957            confidence: 0.95,
958            dependency_patterns: vec!["htmx.org".to_string()],
959            requires: vec![],
960            conflicts_with: vec![],
961            is_primary_indicator: false,
962            alternative_names: vec!["htmx".to_string()],
963            file_indicators: vec![],
964        },
965        
966        // BACKEND FRAMEWORKS
967        TechnologyRule {
968            name: "Express.js".to_string(),
969            category: TechnologyCategory::BackendFramework,
970            confidence: 0.95,
971            dependency_patterns: vec!["express".to_string()],
972            requires: vec![],
973            conflicts_with: vec![],
974            is_primary_indicator: true,
975            alternative_names: vec!["express".to_string()],
976            file_indicators: vec![],
977        },
978        TechnologyRule {
979            name: "Fastify".to_string(),
980            category: TechnologyCategory::BackendFramework,
981            confidence: 0.95,
982            dependency_patterns: vec!["fastify".to_string()],
983            requires: vec![],
984            conflicts_with: vec![],
985            is_primary_indicator: true,
986            alternative_names: vec![],
987            file_indicators: vec![],
988        },
989        TechnologyRule {
990            name: "Nest.js".to_string(),
991            category: TechnologyCategory::BackendFramework,
992            confidence: 0.95,
993            dependency_patterns: vec!["@nestjs/core".to_string()],
994            requires: vec![],
995            conflicts_with: vec![],
996            is_primary_indicator: true,
997            alternative_names: vec!["nestjs".to_string()],
998            file_indicators: vec![],
999        },
1000        TechnologyRule {
1001            name: "Hono".to_string(),
1002            category: TechnologyCategory::BackendFramework,
1003            confidence: 0.95,
1004            dependency_patterns: vec!["hono".to_string()],
1005            requires: vec![],
1006            conflicts_with: vec![],
1007            is_primary_indicator: true,
1008            alternative_names: vec![],
1009            file_indicators: vec![],
1010        },
1011        TechnologyRule {
1012            name: "Elysia".to_string(),
1013            category: TechnologyCategory::BackendFramework,
1014            confidence: 0.95,
1015            dependency_patterns: vec!["elysia".to_string()],
1016            requires: vec![],
1017            conflicts_with: vec![],
1018            is_primary_indicator: true,
1019            alternative_names: vec![],
1020            file_indicators: vec![],
1021        },
1022        TechnologyRule {
1023            name: "Encore".to_string(),
1024            category: TechnologyCategory::BackendFramework,
1025            confidence: 0.95,
1026            dependency_patterns: vec!["encore.dev".to_string(), "encore".to_string()],
1027            requires: vec![],
1028            conflicts_with: vec!["Next.js".to_string()],
1029            is_primary_indicator: true,
1030            alternative_names: vec!["encore-ts-starter".to_string()],
1031            file_indicators: vec!["encore.app".to_string(), "encore.service.ts".to_string(), "encore.service.js".to_string()],
1032        },
1033        
1034        // BUILD TOOLS (Not frameworks!)
1035        TechnologyRule {
1036            name: "Vite".to_string(),
1037            category: TechnologyCategory::BuildTool,
1038            confidence: 0.80,
1039            dependency_patterns: vec!["vite".to_string()],
1040            requires: vec![],
1041            conflicts_with: vec![],
1042            is_primary_indicator: false,
1043            alternative_names: vec![],
1044            file_indicators: vec![],
1045        },
1046        TechnologyRule {
1047            name: "Webpack".to_string(),
1048            category: TechnologyCategory::BuildTool,
1049            confidence: 0.80,
1050            dependency_patterns: vec!["webpack".to_string()],
1051            requires: vec![],
1052            conflicts_with: vec![],
1053            is_primary_indicator: false,
1054            alternative_names: vec![],
1055            file_indicators: vec![],
1056        },
1057        
1058        // DATABASE/ORM (Important for Docker/infrastructure setup, migrations, etc.)
1059        TechnologyRule {
1060            name: "Prisma".to_string(),
1061            category: TechnologyCategory::Database,
1062            confidence: 0.90,
1063            dependency_patterns: vec!["prisma".to_string(), "@prisma/client".to_string()],
1064            requires: vec![],
1065            conflicts_with: vec![],
1066            is_primary_indicator: false,
1067            alternative_names: vec![],
1068            file_indicators: vec![],
1069        },
1070        TechnologyRule {
1071            name: "Drizzle ORM".to_string(),
1072            category: TechnologyCategory::Database,
1073            confidence: 0.90,
1074            dependency_patterns: vec!["drizzle-orm".to_string(), "drizzle-kit".to_string()],
1075            requires: vec![],
1076            conflicts_with: vec![],
1077            is_primary_indicator: false,
1078            alternative_names: vec!["drizzle".to_string()],
1079            file_indicators: vec![],
1080        },
1081        TechnologyRule {
1082            name: "Sequelize".to_string(),
1083            category: TechnologyCategory::Database,
1084            confidence: 0.90,
1085            dependency_patterns: vec!["sequelize".to_string()],
1086            requires: vec![],
1087            conflicts_with: vec![],
1088            is_primary_indicator: false,
1089            alternative_names: vec![],
1090            file_indicators: vec![],
1091        },
1092        TechnologyRule {
1093            name: "TypeORM".to_string(),
1094            category: TechnologyCategory::Database,
1095            confidence: 0.90,
1096            dependency_patterns: vec!["typeorm".to_string()],
1097            requires: vec![],
1098            conflicts_with: vec![],
1099            is_primary_indicator: false,
1100            alternative_names: vec![],
1101            file_indicators: vec![],
1102        },
1103        TechnologyRule {
1104            name: "MikroORM".to_string(),
1105            category: TechnologyCategory::Database,
1106            confidence: 0.90,
1107            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()],
1108            requires: vec![],
1109            conflicts_with: vec![],
1110            is_primary_indicator: false,
1111            alternative_names: vec!["mikro-orm".to_string()],
1112            file_indicators: vec![],
1113        },
1114        TechnologyRule {
1115            name: "Mongoose".to_string(),
1116            category: TechnologyCategory::Database,
1117            confidence: 0.95,
1118            dependency_patterns: vec!["mongoose".to_string()],
1119            requires: vec![],
1120            conflicts_with: vec![],
1121            is_primary_indicator: false,
1122            alternative_names: vec![],
1123            file_indicators: vec![],
1124        },
1125        TechnologyRule {
1126            name: "Typegoose".to_string(),
1127            category: TechnologyCategory::Database,
1128            confidence: 0.90,
1129            dependency_patterns: vec!["@typegoose/typegoose".to_string()],
1130            requires: vec!["Mongoose".to_string()],
1131            conflicts_with: vec![],
1132            is_primary_indicator: false,
1133            alternative_names: vec![],
1134            file_indicators: vec![],
1135        },
1136        TechnologyRule {
1137            name: "Objection.js".to_string(),
1138            category: TechnologyCategory::Database,
1139            confidence: 0.90,
1140            dependency_patterns: vec!["objection".to_string()],
1141            requires: vec!["Knex.js".to_string()],
1142            conflicts_with: vec![],
1143            is_primary_indicator: false,
1144            alternative_names: vec!["objectionjs".to_string()],
1145            file_indicators: vec![],
1146        },
1147        TechnologyRule {
1148            name: "Bookshelf".to_string(),
1149            category: TechnologyCategory::Database,
1150            confidence: 0.85,
1151            dependency_patterns: vec!["bookshelf".to_string()],
1152            requires: vec!["Knex.js".to_string()],
1153            conflicts_with: vec![],
1154            is_primary_indicator: false,
1155            alternative_names: vec![],
1156            file_indicators: vec![],
1157        },
1158        TechnologyRule {
1159            name: "Waterline".to_string(),
1160            category: TechnologyCategory::Database,
1161            confidence: 0.85,
1162            dependency_patterns: vec!["waterline".to_string(), "sails-mysql".to_string(), "sails-postgresql".to_string(), "sails-disk".to_string()],
1163            requires: vec![],
1164            conflicts_with: vec![],
1165            is_primary_indicator: false,
1166            alternative_names: vec![],
1167            file_indicators: vec![],
1168        },
1169        TechnologyRule {
1170            name: "Knex.js".to_string(),
1171            category: TechnologyCategory::Database,
1172            confidence: 0.85,
1173            dependency_patterns: vec!["knex".to_string()],
1174            requires: vec![],
1175            conflicts_with: vec![],
1176            is_primary_indicator: false,
1177            alternative_names: vec!["knexjs".to_string()],
1178            file_indicators: vec![],
1179        },
1180        
1181        // RUNTIMES (Important for IaC - determines base images, package managers)
1182        TechnologyRule {
1183            name: "Node.js".to_string(),
1184            category: TechnologyCategory::Runtime,
1185            confidence: 0.90,
1186            dependency_patterns: vec!["node".to_string()], // This will need file-based detection
1187            requires: vec![],
1188            conflicts_with: vec![],
1189            is_primary_indicator: false,
1190            alternative_names: vec!["nodejs".to_string()],
1191            file_indicators: vec![],
1192        },
1193        TechnologyRule {
1194            name: "Bun".to_string(),
1195            category: TechnologyCategory::Runtime,
1196            confidence: 0.95,
1197            dependency_patterns: vec!["bun".to_string()], // Look for bun in devDependencies or bun.lockb file
1198            requires: vec![],
1199            conflicts_with: vec![],
1200            is_primary_indicator: false,
1201            alternative_names: vec![],
1202            file_indicators: vec![],
1203        },
1204        TechnologyRule {
1205            name: "Deno".to_string(),
1206            category: TechnologyCategory::Runtime,
1207            confidence: 0.95,
1208            dependency_patterns: vec!["@deno/core".to_string(), "deno".to_string()],
1209            requires: vec![],
1210            conflicts_with: vec![],
1211            is_primary_indicator: false,
1212            alternative_names: vec![],
1213            file_indicators: vec![],
1214        },
1215        TechnologyRule {
1216            name: "WinterJS".to_string(),
1217            category: TechnologyCategory::Runtime,
1218            confidence: 0.95,
1219            dependency_patterns: vec!["winterjs".to_string(), "winter-js".to_string()],
1220            requires: vec![],
1221            conflicts_with: vec![],
1222            is_primary_indicator: false,
1223            alternative_names: vec!["winter.js".to_string()],
1224            file_indicators: vec![],
1225        },
1226        TechnologyRule {
1227            name: "Cloudflare Workers".to_string(),
1228            category: TechnologyCategory::Runtime,
1229            confidence: 0.90,
1230            dependency_patterns: vec!["@cloudflare/workers-types".to_string(), "@cloudflare/vitest-pool-workers".to_string(), "wrangler".to_string()],
1231            requires: vec![],
1232            conflicts_with: vec![],
1233            is_primary_indicator: false,
1234            alternative_names: vec!["cloudflare-workers".to_string()],
1235            file_indicators: vec![],
1236        },
1237        TechnologyRule {
1238            name: "Vercel Edge Runtime".to_string(),
1239            category: TechnologyCategory::Runtime,
1240            confidence: 0.90,
1241            dependency_patterns: vec!["@vercel/edge-runtime".to_string(), "@edge-runtime/vm".to_string()],
1242            requires: vec![],
1243            conflicts_with: vec![],
1244            is_primary_indicator: false,
1245            alternative_names: vec!["vercel-edge".to_string()],
1246            file_indicators: vec![],
1247        },
1248        TechnologyRule {
1249            name: "Hermes".to_string(),
1250            category: TechnologyCategory::Runtime,
1251            confidence: 0.85,
1252            dependency_patterns: vec!["hermes-engine".to_string()],
1253            requires: vec!["React Native".to_string()],
1254            conflicts_with: vec![],
1255            is_primary_indicator: false,
1256            alternative_names: vec![],
1257            file_indicators: vec![],
1258        },
1259        TechnologyRule {
1260            name: "Electron".to_string(),
1261            category: TechnologyCategory::Runtime,
1262            confidence: 0.95,
1263            dependency_patterns: vec!["electron".to_string()],
1264            requires: vec![],
1265            conflicts_with: vec![],
1266            is_primary_indicator: false,
1267            alternative_names: vec![],
1268            file_indicators: vec![],
1269        },
1270        TechnologyRule {
1271            name: "Tauri".to_string(),
1272            category: TechnologyCategory::Runtime,
1273            confidence: 0.95,
1274            dependency_patterns: vec!["@tauri-apps/cli".to_string(), "@tauri-apps/api".to_string()],
1275            requires: vec![],
1276            conflicts_with: vec!["Electron".to_string()],
1277            is_primary_indicator: false,
1278            alternative_names: vec![],
1279            file_indicators: vec![],
1280        },
1281        TechnologyRule {
1282            name: "QuickJS".to_string(),
1283            category: TechnologyCategory::Runtime,
1284            confidence: 0.85,
1285            dependency_patterns: vec!["quickjs".to_string(), "quickjs-emscripten".to_string()],
1286            requires: vec![],
1287            conflicts_with: vec![],
1288            is_primary_indicator: false,
1289            alternative_names: vec![],
1290            file_indicators: vec![],
1291        },
1292        
1293        // TESTING (Keep minimal - only major frameworks that affect build process)
1294        TechnologyRule {
1295            name: "Jest".to_string(),
1296            category: TechnologyCategory::Testing,
1297            confidence: 0.85,
1298            dependency_patterns: vec!["jest".to_string()],
1299            requires: vec![],
1300            conflicts_with: vec![],
1301            is_primary_indicator: false,
1302            alternative_names: vec![],
1303            file_indicators: vec![],
1304        },
1305        TechnologyRule {
1306            name: "Vitest".to_string(),
1307            category: TechnologyCategory::Testing,
1308            confidence: 0.85,
1309            dependency_patterns: vec!["vitest".to_string()],
1310            requires: vec![],
1311            conflicts_with: vec![],
1312            is_primary_indicator: false,
1313            alternative_names: vec![],
1314            file_indicators: vec![],
1315        },
1316    ]
1317}