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!["elysia".to_string()],
1159            requires: vec![],
1160            conflicts_with: vec![],
1161            is_primary_indicator: true,
1162            alternative_names: vec![],
1163            file_indicators: vec![],
1164        },
1165        TechnologyRule {
1166            name: "Encore".to_string(),
1167            category: TechnologyCategory::BackendFramework,
1168            confidence: 0.95,
1169            dependency_patterns: vec!["encore.dev".to_string(), "encore".to_string()],
1170            requires: vec![],
1171            conflicts_with: vec!["Next.js".to_string()],
1172            is_primary_indicator: true,
1173            alternative_names: vec!["encore-ts-starter".to_string()],
1174            file_indicators: vec![
1175                "encore.app".to_string(),
1176                "encore.service.ts".to_string(),
1177                "encore.service.js".to_string(),
1178            ],
1179        },
1180        // BUILD TOOLS (Not frameworks!)
1181        TechnologyRule {
1182            name: "Vite".to_string(),
1183            category: TechnologyCategory::BuildTool,
1184            confidence: 0.80,
1185            dependency_patterns: vec!["vite".to_string()],
1186            requires: vec![],
1187            conflicts_with: vec![],
1188            is_primary_indicator: false,
1189            alternative_names: vec![],
1190            file_indicators: vec![],
1191        },
1192        TechnologyRule {
1193            name: "Webpack".to_string(),
1194            category: TechnologyCategory::BuildTool,
1195            confidence: 0.80,
1196            dependency_patterns: vec!["webpack".to_string()],
1197            requires: vec![],
1198            conflicts_with: vec![],
1199            is_primary_indicator: false,
1200            alternative_names: vec![],
1201            file_indicators: vec![],
1202        },
1203        // DATABASE/ORM (Important for Docker/infrastructure setup, migrations, etc.)
1204        TechnologyRule {
1205            name: "Prisma".to_string(),
1206            category: TechnologyCategory::Database,
1207            confidence: 0.90,
1208            dependency_patterns: vec!["prisma".to_string(), "@prisma/client".to_string()],
1209            requires: vec![],
1210            conflicts_with: vec![],
1211            is_primary_indicator: false,
1212            alternative_names: vec![],
1213            file_indicators: vec![],
1214        },
1215        TechnologyRule {
1216            name: "Drizzle ORM".to_string(),
1217            category: TechnologyCategory::Database,
1218            confidence: 0.90,
1219            dependency_patterns: vec!["drizzle-orm".to_string(), "drizzle-kit".to_string()],
1220            requires: vec![],
1221            conflicts_with: vec![],
1222            is_primary_indicator: false,
1223            alternative_names: vec!["drizzle".to_string()],
1224            file_indicators: vec![],
1225        },
1226        TechnologyRule {
1227            name: "Sequelize".to_string(),
1228            category: TechnologyCategory::Database,
1229            confidence: 0.90,
1230            dependency_patterns: vec!["sequelize".to_string()],
1231            requires: vec![],
1232            conflicts_with: vec![],
1233            is_primary_indicator: false,
1234            alternative_names: vec![],
1235            file_indicators: vec![],
1236        },
1237        TechnologyRule {
1238            name: "TypeORM".to_string(),
1239            category: TechnologyCategory::Database,
1240            confidence: 0.90,
1241            dependency_patterns: vec!["typeorm".to_string()],
1242            requires: vec![],
1243            conflicts_with: vec![],
1244            is_primary_indicator: false,
1245            alternative_names: vec![],
1246            file_indicators: vec![],
1247        },
1248        TechnologyRule {
1249            name: "MikroORM".to_string(),
1250            category: TechnologyCategory::Database,
1251            confidence: 0.90,
1252            dependency_patterns: vec![
1253                "@mikro-orm/core".to_string(),
1254                "@mikro-orm/postgresql".to_string(),
1255                "@mikro-orm/mysql".to_string(),
1256                "@mikro-orm/sqlite".to_string(),
1257                "@mikro-orm/mongodb".to_string(),
1258            ],
1259            requires: vec![],
1260            conflicts_with: vec![],
1261            is_primary_indicator: false,
1262            alternative_names: vec!["mikro-orm".to_string()],
1263            file_indicators: vec![],
1264        },
1265        TechnologyRule {
1266            name: "Mongoose".to_string(),
1267            category: TechnologyCategory::Database,
1268            confidence: 0.95,
1269            dependency_patterns: vec!["mongoose".to_string()],
1270            requires: vec![],
1271            conflicts_with: vec![],
1272            is_primary_indicator: false,
1273            alternative_names: vec![],
1274            file_indicators: vec![],
1275        },
1276        TechnologyRule {
1277            name: "Typegoose".to_string(),
1278            category: TechnologyCategory::Database,
1279            confidence: 0.90,
1280            dependency_patterns: vec!["@typegoose/typegoose".to_string()],
1281            requires: vec!["Mongoose".to_string()],
1282            conflicts_with: vec![],
1283            is_primary_indicator: false,
1284            alternative_names: vec![],
1285            file_indicators: vec![],
1286        },
1287        TechnologyRule {
1288            name: "Objection.js".to_string(),
1289            category: TechnologyCategory::Database,
1290            confidence: 0.90,
1291            dependency_patterns: vec!["objection".to_string()],
1292            requires: vec!["Knex.js".to_string()],
1293            conflicts_with: vec![],
1294            is_primary_indicator: false,
1295            alternative_names: vec!["objectionjs".to_string()],
1296            file_indicators: vec![],
1297        },
1298        TechnologyRule {
1299            name: "Bookshelf".to_string(),
1300            category: TechnologyCategory::Database,
1301            confidence: 0.85,
1302            dependency_patterns: vec!["bookshelf".to_string()],
1303            requires: vec!["Knex.js".to_string()],
1304            conflicts_with: vec![],
1305            is_primary_indicator: false,
1306            alternative_names: vec![],
1307            file_indicators: vec![],
1308        },
1309        TechnologyRule {
1310            name: "Waterline".to_string(),
1311            category: TechnologyCategory::Database,
1312            confidence: 0.85,
1313            dependency_patterns: vec![
1314                "waterline".to_string(),
1315                "sails-mysql".to_string(),
1316                "sails-postgresql".to_string(),
1317                "sails-disk".to_string(),
1318            ],
1319            requires: vec![],
1320            conflicts_with: vec![],
1321            is_primary_indicator: false,
1322            alternative_names: vec![],
1323            file_indicators: vec![],
1324        },
1325        TechnologyRule {
1326            name: "Knex.js".to_string(),
1327            category: TechnologyCategory::Database,
1328            confidence: 0.85,
1329            dependency_patterns: vec!["knex".to_string()],
1330            requires: vec![],
1331            conflicts_with: vec![],
1332            is_primary_indicator: false,
1333            alternative_names: vec!["knexjs".to_string()],
1334            file_indicators: vec![],
1335        },
1336        // RUNTIMES (Important for IaC - determines base images, package managers)
1337        TechnologyRule {
1338            name: "Node.js".to_string(),
1339            category: TechnologyCategory::Runtime,
1340            confidence: 0.90,
1341            dependency_patterns: vec!["node".to_string()], // This will need file-based detection
1342            requires: vec![],
1343            conflicts_with: vec![],
1344            is_primary_indicator: false,
1345            alternative_names: vec!["nodejs".to_string()],
1346            file_indicators: vec![],
1347        },
1348        TechnologyRule {
1349            name: "Bun".to_string(),
1350            category: TechnologyCategory::Runtime,
1351            confidence: 0.95,
1352            dependency_patterns: vec!["bun".to_string()], // Look for bun in devDependencies or bun.lockb file
1353            requires: vec![],
1354            conflicts_with: vec![],
1355            is_primary_indicator: false,
1356            alternative_names: vec![],
1357            file_indicators: vec![],
1358        },
1359        TechnologyRule {
1360            name: "Deno".to_string(),
1361            category: TechnologyCategory::Runtime,
1362            confidence: 0.95,
1363            dependency_patterns: vec!["@deno/core".to_string(), "deno".to_string()],
1364            requires: vec![],
1365            conflicts_with: vec![],
1366            is_primary_indicator: false,
1367            alternative_names: vec![],
1368            file_indicators: vec![],
1369        },
1370        TechnologyRule {
1371            name: "WinterJS".to_string(),
1372            category: TechnologyCategory::Runtime,
1373            confidence: 0.95,
1374            dependency_patterns: vec!["winterjs".to_string(), "winter-js".to_string()],
1375            requires: vec![],
1376            conflicts_with: vec![],
1377            is_primary_indicator: false,
1378            alternative_names: vec!["winter.js".to_string()],
1379            file_indicators: vec![],
1380        },
1381        TechnologyRule {
1382            name: "Cloudflare Workers".to_string(),
1383            category: TechnologyCategory::Runtime,
1384            confidence: 0.90,
1385            dependency_patterns: vec![
1386                "@cloudflare/workers-types".to_string(),
1387                "@cloudflare/vitest-pool-workers".to_string(),
1388                "wrangler".to_string(),
1389            ],
1390            requires: vec![],
1391            conflicts_with: vec![],
1392            is_primary_indicator: false,
1393            alternative_names: vec!["cloudflare-workers".to_string()],
1394            file_indicators: vec![],
1395        },
1396        TechnologyRule {
1397            name: "Vercel Edge Runtime".to_string(),
1398            category: TechnologyCategory::Runtime,
1399            confidence: 0.90,
1400            dependency_patterns: vec![
1401                "@vercel/edge-runtime".to_string(),
1402                "@edge-runtime/vm".to_string(),
1403            ],
1404            requires: vec![],
1405            conflicts_with: vec![],
1406            is_primary_indicator: false,
1407            alternative_names: vec!["vercel-edge".to_string()],
1408            file_indicators: vec![],
1409        },
1410        TechnologyRule {
1411            name: "Hermes".to_string(),
1412            category: TechnologyCategory::Runtime,
1413            confidence: 0.85,
1414            dependency_patterns: vec!["hermes-engine".to_string()],
1415            requires: vec!["React Native".to_string()],
1416            conflicts_with: vec![],
1417            is_primary_indicator: false,
1418            alternative_names: vec![],
1419            file_indicators: vec![],
1420        },
1421        TechnologyRule {
1422            name: "Electron".to_string(),
1423            category: TechnologyCategory::Runtime,
1424            confidence: 0.95,
1425            dependency_patterns: vec!["electron".to_string()],
1426            requires: vec![],
1427            conflicts_with: vec![],
1428            is_primary_indicator: false,
1429            alternative_names: vec![],
1430            file_indicators: vec![],
1431        },
1432        TechnologyRule {
1433            name: "Tauri".to_string(),
1434            category: TechnologyCategory::Runtime,
1435            confidence: 0.95,
1436            dependency_patterns: vec!["@tauri-apps/cli".to_string(), "@tauri-apps/api".to_string()],
1437            requires: vec![],
1438            conflicts_with: vec!["Electron".to_string()],
1439            is_primary_indicator: false,
1440            alternative_names: vec![],
1441            file_indicators: vec![],
1442        },
1443        TechnologyRule {
1444            name: "QuickJS".to_string(),
1445            category: TechnologyCategory::Runtime,
1446            confidence: 0.85,
1447            dependency_patterns: vec!["quickjs".to_string(), "quickjs-emscripten".to_string()],
1448            requires: vec![],
1449            conflicts_with: vec![],
1450            is_primary_indicator: false,
1451            alternative_names: vec![],
1452            file_indicators: vec![],
1453        },
1454        // TESTING (Keep minimal - only major frameworks that affect build process)
1455        TechnologyRule {
1456            name: "Jest".to_string(),
1457            category: TechnologyCategory::Testing,
1458            confidence: 0.85,
1459            dependency_patterns: vec!["jest".to_string()],
1460            requires: vec![],
1461            conflicts_with: vec![],
1462            is_primary_indicator: false,
1463            alternative_names: vec![],
1464            file_indicators: vec![],
1465        },
1466        TechnologyRule {
1467            name: "Vitest".to_string(),
1468            category: TechnologyCategory::Testing,
1469            confidence: 0.85,
1470            dependency_patterns: vec!["vitest".to_string()],
1471            requires: vec![],
1472            conflicts_with: vec![],
1473            is_primary_indicator: false,
1474            alternative_names: vec![],
1475            file_indicators: vec![],
1476        },
1477    ]
1478}