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