@@ -172,16 +172,25 @@ function detectStack(files: FileInfo[]): StackInfo {
172172 }
173173
174174 // Framework/build system detection from config files
175+ // NOTE: Frameworks like React, Express, Flask are NOT detected by file path patterns
176+ // as that causes false positives. They should be detected via dependency analysis (deps.ts).
177+ // Only config files that definitively indicate framework usage are listed here.
175178 const configPatterns : Record < string , { file : RegExp | string ; type : "framework" | "build" | "pm" } > = {
176179 "Next.js" : { file : "next.config.js" , type : "framework" } ,
177180 "Next.js (mjs)" : { file : "next.config.mjs" , type : "framework" } ,
178- React : { file : / r e a c t / , type : "framework" } ,
181+ "Next.js (ts)" : { file : "next.config.ts" , type : "framework" } ,
179182 Vue : { file : "vue.config.js" , type : "framework" } ,
183+ "Nuxt.js" : { file : "nuxt.config.js" , type : "framework" } ,
184+ "Nuxt.js (ts)" : { file : "nuxt.config.ts" , type : "framework" } ,
180185 Angular : { file : "angular.json" , type : "framework" } ,
181- Express : { file : / e x p r e s s / , type : "framework" } ,
182186 Django : { file : "manage.py" , type : "framework" } ,
183- Flask : { file : / f l a s k / , type : "framework" } ,
184187 Rails : { file : "Gemfile" , type : "framework" } ,
188+ Astro : { file : "astro.config.mjs" , type : "framework" } ,
189+ "Astro (ts)" : { file : "astro.config.ts" , type : "framework" } ,
190+ Remix : { file : "remix.config.js" , type : "framework" } ,
191+ SvelteKit : { file : "svelte.config.js" , type : "framework" } ,
192+ Vite : { file : "vite.config.ts" , type : "build" } ,
193+ "Vite (js)" : { file : "vite.config.js" , type : "build" } ,
185194 npm : { file : "package-lock.json" , type : "pm" } ,
186195 yarn : { file : "yarn.lock" , type : "pm" } ,
187196 pnpm : { file : "pnpm-lock.yaml" , type : "pm" } ,
@@ -191,6 +200,7 @@ function detectStack(files: FileInfo[]): StackInfo {
191200 cargo : { file : "Cargo.toml" , type : "build" } ,
192201 maven : { file : "pom.xml" , type : "build" } ,
193202 gradle : { file : "build.gradle" , type : "build" } ,
203+ "gradle (kts)" : { file : "build.gradle.kts" , type : "build" } ,
194204 make : { file : "Makefile" , type : "build" } ,
195205 Lake : { file : "lakefile.toml" , type : "build" } ,
196206 "Lake (lean)" : { file : "lakefile.lean" , type : "build" } ,
@@ -200,6 +210,10 @@ function detectStack(files: FileInfo[]): StackInfo {
200210 sbt : { file : "build.sbt" , type : "build" } ,
201211 CMake : { file : "CMakeLists.txt" , type : "build" } ,
202212 zig : { file : "build.zig" , type : "build" } ,
213+ Webpack : { file : "webpack.config.js" , type : "build" } ,
214+ Rollup : { file : "rollup.config.js" , type : "build" } ,
215+ esbuild : { file : "esbuild.config.js" , type : "build" } ,
216+ tsup : { file : "tsup.config.ts" , type : "build" } ,
203217 } ;
204218
205219 for ( const [ name , { file, type } ] of Object . entries ( configPatterns ) ) {
@@ -209,12 +223,14 @@ function detectStack(files: FileInfo[]): StackInfo {
209223 : filePaths . some ( ( p ) => file . test ( p ) ) ;
210224
211225 if ( matches ) {
212- if ( type === "framework" && ! stack . frameworks . includes ( name . replace ( " (mjs)" , "" ) ) ) {
213- stack . frameworks . push ( name . replace ( " (mjs)" , "" ) ) ;
226+ // Normalize framework names by removing variant suffixes like (mjs), (ts), etc.
227+ const normalizedName = name . replace ( / \s * \( [ ^ ) ] + \) $ / , "" ) ;
228+ if ( type === "framework" && ! stack . frameworks . includes ( normalizedName ) ) {
229+ stack . frameworks . push ( normalizedName ) ;
214230 } else if ( type === "build" && ! stack . buildSystem ) {
215- stack . buildSystem = name ;
231+ stack . buildSystem = normalizedName ;
216232 } else if ( type === "pm" && ! stack . packageManager ) {
217- stack . packageManager = name ;
233+ stack . packageManager = normalizedName ;
218234 }
219235 }
220236 }
@@ -541,3 +557,96 @@ export function listFilesByPattern(files: FileInfo[], pattern: string): string[]
541557 const regex = new RegExp ( `^${ regexPattern } $` ) ;
542558 return files . filter ( ( f ) => ! f . isDirectory && regex . test ( f . path ) ) . map ( ( f ) => f . path ) ;
543559}
560+
561+ /**
562+ * Known frameworks that can be detected from dependencies
563+ * Maps dependency name to display name
564+ */
565+ const DEPENDENCY_FRAMEWORKS : Record < string , string > = {
566+ // JavaScript/TypeScript frameworks
567+ "react" : "React" ,
568+ "react-dom" : "React" ,
569+ "vue" : "Vue" ,
570+ "angular" : "Angular" ,
571+ "@angular/core" : "Angular" ,
572+ "svelte" : "Svelte" ,
573+ "solid-js" : "Solid" ,
574+ "preact" : "Preact" ,
575+ "express" : "Express" ,
576+ "fastify" : "Fastify" ,
577+ "hono" : "Hono" ,
578+ "koa" : "Koa" ,
579+ "hapi" : "Hapi" ,
580+ "@hapi/hapi" : "Hapi" ,
581+ "nest" : "NestJS" ,
582+ "@nestjs/core" : "NestJS" ,
583+ "next" : "Next.js" ,
584+ "gatsby" : "Gatsby" ,
585+ "nuxt" : "Nuxt.js" ,
586+ "remix" : "Remix" ,
587+ "@remix-run/node" : "Remix" ,
588+ "astro" : "Astro" ,
589+ "electron" : "Electron" ,
590+ // Python frameworks
591+ "flask" : "Flask" ,
592+ "django" : "Django" ,
593+ "fastapi" : "FastAPI" ,
594+ "tornado" : "Tornado" ,
595+ "pyramid" : "Pyramid" ,
596+ "bottle" : "Bottle" ,
597+ // Go frameworks (from go.mod require)
598+ "github.com/gin-gonic/gin" : "Gin" ,
599+ "github.com/gorilla/mux" : "Gorilla Mux" ,
600+ "github.com/labstack/echo" : "Echo" ,
601+ "github.com/gofiber/fiber" : "Fiber" ,
602+ // Ruby frameworks
603+ "rails" : "Rails" ,
604+ "sinatra" : "Sinatra" ,
605+ // Rust frameworks
606+ "actix-web" : "Actix Web" ,
607+ "rocket" : "Rocket" ,
608+ "axum" : "Axum" ,
609+ } ;
610+
611+ /**
612+ * Detect frameworks from dependency list
613+ * This is more accurate than file path matching
614+ */
615+ export function detectFrameworksFromDeps ( depNames : string [ ] ) : string [ ] {
616+ const frameworks = new Set < string > ( ) ;
617+
618+ for ( const dep of depNames ) {
619+ const normalized = dep . toLowerCase ( ) ;
620+ if ( DEPENDENCY_FRAMEWORKS [ normalized ] ) {
621+ frameworks . add ( DEPENDENCY_FRAMEWORKS [ normalized ] ) ;
622+ }
623+ // Also check the full dep name (for Go modules, etc.)
624+ if ( DEPENDENCY_FRAMEWORKS [ dep ] ) {
625+ frameworks . add ( DEPENDENCY_FRAMEWORKS [ dep ] ) ;
626+ }
627+ }
628+
629+ return Array . from ( frameworks ) ;
630+ }
631+
632+ /**
633+ * Merge frameworks detected from dependencies into stack info
634+ * Should be called after extractDependencies() in the main flow
635+ */
636+ export function mergeFrameworksFromDeps (
637+ stack : StackInfo ,
638+ depNames : string [ ]
639+ ) : StackInfo {
640+ const depFrameworks = detectFrameworksFromDeps ( depNames ) ;
641+ const existingNormalized = new Set (
642+ stack . frameworks . map ( f => f . toLowerCase ( ) )
643+ ) ;
644+
645+ for ( const framework of depFrameworks ) {
646+ if ( ! existingNormalized . has ( framework . toLowerCase ( ) ) ) {
647+ stack . frameworks . push ( framework ) ;
648+ }
649+ }
650+
651+ return stack ;
652+ }
0 commit comments