Skip to main content

claude_native/detection/
mod.rs

1pub mod domain;
2pub mod signals;
3pub mod signals_backend;
4
5use crate::scan::ProjectContext;
6
7/// Primary project type detected from heuristics
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum PrimaryType {
10    Standard,
11    Monorepo,
12    MicroRepo,
13    Mobile(MobileFramework),
14    Frontend(FrontendFramework),
15    Backend(BackendFramework),
16    IaC(IaCTool),
17    Serverless(ServerlessPlatform),
18    ML,
19    CodegenHeavy,
20    DocSite(DocFramework),
21    GameDev(GameEngine),
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum MobileFramework {
26    Flutter,
27    ReactNative,
28    IosNative,
29    AndroidNative,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum FrontendFramework {
34    NextJs,
35    Nuxt,
36    Angular,
37    VueVite,
38    CreateReactApp,
39    Other,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum BackendFramework {
44    Django,
45    Rails,
46    Express,
47    GoService,
48    RustService,
49    Phoenix,
50    Other,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum IaCTool {
55    Terraform,
56    Helm,
57    Kustomize,
58    Pulumi,
59    AwsCdk,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum ServerlessPlatform {
64    ServerlessFramework,
65    AwsSam,
66    CloudflareWorkers,
67    VercelFunctions,
68    NetlifyFunctions,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum DocFramework {
73    Docusaurus,
74    MkDocs,
75    Hugo,
76    VuePress,
77    Other,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum GameEngine {
82    Unity,
83    Godot,
84    Bevy,
85    Unreal,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum Language {
90    Rust,
91    TypeScript,
92    JavaScript,
93    Python,
94    Go,
95    Dart,
96    Swift,
97    Kotlin,
98    Java,
99    Ruby,
100    CSharp,
101    Elixir,
102    Cpp,
103    Other(String),
104}
105
106/// Compound flags that overlay on PrimaryType
107#[derive(Debug, Clone, Default)]
108pub struct ProjectFlags {
109    pub is_polyglot: bool,
110    pub is_legacy: bool,
111}
112
113/// Full detection result
114#[derive(Debug, Clone)]
115pub struct ProjectType {
116    pub primary: PrimaryType,
117    pub flags: ProjectFlags,
118    pub languages: Vec<Language>,
119}
120
121impl std::fmt::Display for ProjectType {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        let primary = match &self.primary {
124            PrimaryType::Standard => "Standard".to_string(),
125            PrimaryType::Monorepo => "Monorepo".to_string(),
126            PrimaryType::MicroRepo => "Micro-repo".to_string(),
127            PrimaryType::Mobile(fw) => format!("Mobile ({fw:?})"),
128            PrimaryType::Frontend(fw) => format!("Frontend ({fw:?})"),
129            PrimaryType::Backend(fw) => format!("Backend ({fw:?})"),
130            PrimaryType::IaC(tool) => format!("IaC ({tool:?})"),
131            PrimaryType::Serverless(p) => format!("Serverless ({p:?})"),
132            PrimaryType::ML => "Data Science / ML".to_string(),
133            PrimaryType::CodegenHeavy => "Code Generation Heavy".to_string(),
134            PrimaryType::DocSite(fw) => format!("Documentation Site ({fw:?})"),
135            PrimaryType::GameDev(eng) => format!("Game Development ({eng:?})"),
136        };
137
138        let mut flags = Vec::new();
139        if self.flags.is_polyglot {
140            flags.push("Polyglot");
141        }
142        if self.flags.is_legacy {
143            flags.push("Legacy");
144        }
145
146        if flags.is_empty() {
147            write!(f, "{primary}")
148        } else {
149            write!(f, "{primary} + {}", flags.join(" + "))
150        }
151    }
152}
153
154/// Detect the project type from a fully scanned ProjectContext.
155pub fn detect(ctx: &ProjectContext) -> ProjectType {
156    let languages = signals::detect_languages(ctx);
157    let flags = detect_flags(ctx, &languages);
158
159    // Phase 1: Structure check — monorepo
160    if signals::is_monorepo(ctx) {
161        return ProjectType {
162            primary: PrimaryType::Monorepo,
163            flags,
164            languages,
165        };
166    }
167
168    // Phase 2: Domain-specific markers (before micro-repo, since a small
169    // Flutter/Django/Terraform project is still domain-specific, not a generic micro-repo)
170    if let Some(primary) = domain::detect_domain(ctx) {
171        return ProjectType {
172            primary,
173            flags,
174            languages,
175        };
176    }
177
178    // Phase 3: Micro-repo (only if no domain marker matched)
179    if signals::is_micro_repo(ctx) {
180        return ProjectType {
181            primary: PrimaryType::MicroRepo,
182            flags,
183            languages,
184        };
185    }
186
187    ProjectType {
188        primary: PrimaryType::Standard,
189        flags,
190        languages,
191    }
192}
193
194fn detect_flags(ctx: &ProjectContext, languages: &[Language]) -> ProjectFlags {
195    let is_polyglot = languages.len() >= 2;
196
197    let is_legacy = ctx.test_files.is_empty()
198        && ctx.source_file_count() > 10
199        && ctx.average_source_file_lines() > 300.0
200        && !ctx.has_claude_md();
201
202    ProjectFlags {
203        is_polyglot,
204        is_legacy,
205    }
206}