syncable_cli/analyzer/frameworks/
javascript.rs

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