1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
pub mod go;
pub mod java;
pub mod javascript;
pub mod python;
pub mod rust;
use crate::analyzer::{DetectedLanguage, DetectedTechnology};
use crate::error::Result;
use std::collections::HashMap;
/// Common interface for language-specific framework detection
pub trait LanguageFrameworkDetector {
/// Detect frameworks for a specific language
fn detect_frameworks(&self, language: &DetectedLanguage) -> Result<Vec<DetectedTechnology>>;
/// Get the supported language name(s) for this detector
fn supported_languages(&self) -> Vec<&'static str>;
}
/// Technology detection rules with proper classification and relationships
#[derive(Clone, Debug)]
pub struct TechnologyRule {
pub name: String,
pub category: crate::analyzer::TechnologyCategory,
pub confidence: f32,
pub dependency_patterns: Vec<String>,
/// Dependencies this technology requires (e.g., Next.js requires React)
pub requires: Vec<String>,
/// Technologies that conflict with this one (mutually exclusive)
pub conflicts_with: Vec<String>,
/// Whether this technology typically drives the architecture
pub is_primary_indicator: bool,
/// Alternative names for this technology
pub alternative_names: Vec<String>,
/// File indicators that can help identify this technology
pub file_indicators: Vec<String>,
}
/// Shared utilities for framework detection across languages
pub struct FrameworkDetectionUtils;
impl FrameworkDetectionUtils {
/// Generic technology detection based on dependency patterns
pub fn detect_technologies_by_dependencies(
rules: &[TechnologyRule],
dependencies: &[String],
base_confidence: f32,
) -> Vec<DetectedTechnology> {
let mut technologies = Vec::new();
// Debug logging for Tanstack Start detection
let tanstack_deps: Vec<_> = dependencies
.iter()
.filter(|dep| dep.contains("tanstack") || dep.contains("vinxi"))
.collect();
if !tanstack_deps.is_empty() {
log::debug!("Found potential Tanstack dependencies: {:?}", tanstack_deps);
}
for rule in rules {
let mut matches = 0;
let total_patterns = rule.dependency_patterns.len();
if total_patterns == 0 {
continue;
}
for pattern in &rule.dependency_patterns {
let matching_deps: Vec<_> = dependencies
.iter()
.filter(|dep| Self::matches_pattern(dep, pattern))
.collect();
if !matching_deps.is_empty() {
matches += 1;
// Debug logging for Tanstack Start specifically
if rule.name.contains("Tanstack") {
log::debug!(
"Tanstack Start: Pattern '{}' matched dependencies: {:?}",
pattern,
matching_deps
);
}
}
}
// Calculate confidence based on pattern matches and base language confidence
if matches > 0 {
let pattern_confidence = matches as f32 / total_patterns as f32;
// Use additive approach instead of multiplicative to avoid extremely low scores
// Base confidence provides a floor, pattern confidence provides the scaling
// Cap dependency-based confidence at 0.95 to ensure file-based detection (1.0) takes precedence
let final_confidence =
(rule.confidence * pattern_confidence + base_confidence * 0.1).min(0.95);
// Debug logging for Tanstack Start detection
if rule.name.contains("Tanstack") {
log::debug!(
"Tanstack Start detected with {} matches out of {} patterns, confidence: {:.2}",
matches,
total_patterns,
final_confidence
);
}
technologies.push(DetectedTechnology {
name: rule.name.clone(),
version: None, // TODO: Extract version from dependencies
category: rule.category.clone(),
confidence: final_confidence,
requires: rule.requires.clone(),
conflicts_with: rule.conflicts_with.clone(),
is_primary: rule.is_primary_indicator,
file_indicators: rule.file_indicators.clone(),
});
} else if rule.name.contains("Tanstack") {
// Debug logging when Tanstack Start is not detected
log::debug!(
"Tanstack Start not detected - no patterns matched. Available dependencies: {:?}",
dependencies.iter().take(10).collect::<Vec<_>>()
);
}
}
technologies
}
/// Check if a dependency matches a pattern (supports wildcards)
pub fn matches_pattern(dependency: &str, pattern: &str) -> bool {
if pattern.contains('*') {
// Simple wildcard matching
let parts: Vec<&str> = pattern.split('*').collect();
if parts.len() == 2 {
dependency.starts_with(parts[0]) && dependency.ends_with(parts[1])
} else {
dependency.contains(&pattern.replace('*', ""))
}
} else {
// For dependency detection, use exact matching to avoid false positives
// Only match if the dependency is exactly the pattern or starts with the pattern followed by a version specifier
dependency == pattern
|| dependency.starts_with(&(pattern.to_string() + "@"))
|| dependency.starts_with(&(pattern.to_string() + "/"))
// Java/Maven style: spring-boot matches spring-boot-starter-web
|| dependency.starts_with(&(pattern.to_string() + "-"))
// Maven groupId:artifactId style: org.springframework matches org.springframework.boot:spring-boot
|| dependency.starts_with(&(pattern.to_string() + "."))
|| dependency.starts_with(&(pattern.to_string() + ":"))
// Maven artifactId contains the pattern (e.g., "spring" in "spring-boot-starter-web")
|| dependency.contains(&format!("-{}-", pattern))
|| dependency.contains(&format!(":{}", pattern))
}
}
/// Resolves conflicts between mutually exclusive technologies
pub fn resolve_technology_conflicts(
technologies: Vec<DetectedTechnology>,
) -> Vec<DetectedTechnology> {
let mut resolved = Vec::new();
let mut name_to_tech: HashMap<String, DetectedTechnology> = HashMap::new();
// First pass: collect all technologies
for tech in technologies {
if let Some(existing) = name_to_tech.get(&tech.name) {
// Keep the one with higher confidence
if tech.confidence > existing.confidence {
name_to_tech.insert(tech.name.clone(), tech);
}
} else {
name_to_tech.insert(tech.name.clone(), tech);
}
}
// Second pass: resolve conflicts
let all_techs: Vec<_> = name_to_tech.values().collect();
let mut excluded_names = std::collections::HashSet::new();
for tech in &all_techs {
if excluded_names.contains(&tech.name) {
continue;
}
// Check for conflicts
for conflict in &tech.conflicts_with {
if let Some(conflicting_tech) = name_to_tech.get(conflict) {
if tech.confidence > conflicting_tech.confidence {
excluded_names.insert(conflict.clone());
log::info!(
"Excluding {} (confidence: {}) in favor of {} (confidence: {})",
conflict,
conflicting_tech.confidence,
tech.name,
tech.confidence
);
} else {
excluded_names.insert(tech.name.clone());
log::info!(
"Excluding {} (confidence: {}) in favor of {} (confidence: {})",
tech.name,
tech.confidence,
conflict,
conflicting_tech.confidence
);
break;
}
}
}
}
// Collect non-excluded technologies
for tech in name_to_tech.into_values() {
if !excluded_names.contains(&tech.name) {
resolved.push(tech);
}
}
resolved
}
/// Marks technologies that are primary drivers of the application architecture
pub fn mark_primary_technologies(
mut technologies: Vec<DetectedTechnology>,
) -> Vec<DetectedTechnology> {
use crate::analyzer::TechnologyCategory;
// Meta-frameworks are always primary
let mut has_meta_framework = false;
for tech in &mut technologies {
if matches!(tech.category, TechnologyCategory::MetaFramework) {
tech.is_primary = true;
has_meta_framework = true;
}
}
// If no meta-framework, mark the highest confidence backend or frontend framework as primary
if !has_meta_framework {
let mut best_framework: Option<usize> = None;
let mut best_confidence = 0.0;
for (i, tech) in technologies.iter().enumerate() {
if matches!(
tech.category,
TechnologyCategory::BackendFramework | TechnologyCategory::FrontendFramework
) && tech.confidence > best_confidence
{
best_confidence = tech.confidence;
best_framework = Some(i);
}
}
if let Some(index) = best_framework {
technologies[index].is_primary = true;
}
}
technologies
}
}