@@ -136,9 +136,13 @@ struct DetectedStack {
136136 has_vitest : bool ,
137137 has_jest : bool ,
138138 has_eslint : bool ,
139+ has_biome : bool ,
139140 has_python : bool ,
140141 has_pytest : bool ,
141142 has_ruff : bool ,
143+ has_uv : bool ,
144+ has_bun : bool ,
145+ has_pnpm : bool ,
142146 has_go : bool ,
143147 has_ruby : bool ,
144148 has_rails : bool ,
@@ -153,9 +157,13 @@ fn detect_stack(root: &std::path::Path) -> DetectedStack {
153157 has_vitest : false ,
154158 has_jest : false ,
155159 has_eslint : false ,
160+ has_biome : false ,
156161 has_python : false ,
157162 has_pytest : false ,
158163 has_ruff : false ,
164+ has_uv : false ,
165+ has_bun : false ,
166+ has_pnpm : false ,
159167 has_go : false ,
160168 has_ruby : false ,
161169 has_rails : false ,
@@ -170,43 +178,73 @@ fn detect_stack(root: &std::path::Path) -> DetectedStack {
170178 }
171179 }
172180
173- // Node / JS / TS
181+ // Node / JS / TS — detect modern toolchain (bun > pnpm > npm)
174182 if root. join ( "package.json" ) . exists ( ) {
175183 s. has_node = true ;
176184 let pj = fs:: read_to_string ( root. join ( "package.json" ) ) . unwrap_or_default ( ) ;
185+
186+ // Detect package manager (modern first)
187+ if root. join ( "bun.lockb" ) . exists ( ) || root. join ( "bun.lock" ) . exists ( ) {
188+ s. has_bun = true ;
189+ s. stack_lines . push ( "- Package Manager: Bun" . to_string ( ) ) ;
190+ } else if root. join ( "pnpm-lock.yaml" ) . exists ( ) {
191+ s. has_pnpm = true ;
192+ s. stack_lines . push ( "- Package Manager: pnpm" . to_string ( ) ) ;
193+ } else {
194+ s. stack_lines . push ( "- Package Manager: npm" . to_string ( ) ) ;
195+ }
196+
197+ // Detect framework
177198 if pj. contains ( "\" react\" " ) || pj. contains ( "\" next\" " ) {
178199 s. stack_lines . push ( "- Framework: React / Next.js" . to_string ( ) ) ;
179- } else if pj. contains ( "\" vue\" " ) {
180- s. stack_lines . push ( "- Framework: Vue" . to_string ( ) ) ;
181- } else if pj. contains ( "\" svelte\" " ) {
182- s. stack_lines . push ( "- Framework: Svelte " . to_string ( ) ) ;
200+ } else if pj. contains ( "\" vue\" " ) || pj . contains ( " \" nuxt \" " ) {
201+ s. stack_lines . push ( "- Framework: Vue / Nuxt " . to_string ( ) ) ;
202+ } else if pj. contains ( "\" svelte\" " ) || pj . contains ( " \" @sveltejs/kit \" " ) {
203+ s. stack_lines . push ( "- Framework: SvelteKit " . to_string ( ) ) ;
183204 }
205+
184206 if pj. contains ( "\" typescript\" " ) {
185207 s. has_typescript = true ;
186208 s. stack_lines . push ( "- Language: TypeScript" . to_string ( ) ) ;
187209 } else {
188210 s. stack_lines . push ( "- Language: JavaScript" . to_string ( ) ) ;
189211 }
212+
213+ // Detect testing (modern first: vitest > jest)
190214 if pj. contains ( "\" vitest\" " ) {
191215 s. has_vitest = true ;
192216 s. stack_lines . push ( "- Testing: Vitest" . to_string ( ) ) ;
193217 } else if pj. contains ( "\" jest\" " ) {
194218 s. has_jest = true ;
195- s. stack_lines . push ( "- Testing: Jest" . to_string ( ) ) ;
196- }
197- if pj. contains ( "\" eslint\" " ) {
198- s. has_eslint = true ;
199- s. stack_lines . push ( "- Linting: ESLint" . to_string ( ) ) ;
219+ s. stack_lines . push ( "- Testing: Jest (consider migrating to Vitest)" . to_string ( ) ) ;
200220 }
201- if pj. contains ( "\" prettier\" " ) {
202- s. stack_lines . push ( "- Formatting: Prettier" . to_string ( ) ) ;
221+
222+ // Detect linting (modern first: biome > eslint)
223+ if pj. contains ( "\" @biomejs/biome\" " ) || root. join ( "biome.json" ) . exists ( ) || root. join ( "biome.jsonc" ) . exists ( ) {
224+ s. has_biome = true ;
225+ s. stack_lines . push ( "- Linting + Formatting: Biome" . to_string ( ) ) ;
226+ } else {
227+ if pj. contains ( "\" eslint\" " ) {
228+ s. has_eslint = true ;
229+ s. stack_lines . push ( "- Linting: ESLint" . to_string ( ) ) ;
230+ }
231+ if pj. contains ( "\" prettier\" " ) {
232+ s. stack_lines . push ( "- Formatting: Prettier" . to_string ( ) ) ;
233+ }
203234 }
204235 }
205236
206- // Python
237+ // Python — detect modern toolchain (uv > pip)
207238 if root. join ( "pyproject.toml" ) . exists ( ) || root. join ( "setup.py" ) . exists ( ) || root. join ( "requirements.txt" ) . exists ( ) {
208239 s. has_python = true ;
209240 s. stack_lines . push ( "- Language: Python" . to_string ( ) ) ;
241+
242+ // Detect package manager (modern first: uv > pip)
243+ if root. join ( "uv.lock" ) . exists ( ) || root. join ( ".python-version" ) . exists ( ) {
244+ s. has_uv = true ;
245+ s. stack_lines . push ( "- Package Manager: uv" . to_string ( ) ) ;
246+ }
247+
210248 let pyp = fs:: read_to_string ( root. join ( "pyproject.toml" ) ) . unwrap_or_default ( ) ;
211249 if pyp. contains ( "django" ) { s. stack_lines . push ( "- Framework: Django" . to_string ( ) ) ; }
212250 if pyp. contains ( "fastapi" ) { s. stack_lines . push ( "- Framework: FastAPI" . to_string ( ) ) ; }
@@ -215,9 +253,10 @@ fn detect_stack(root: &std::path::Path) -> DetectedStack {
215253 s. has_pytest = true ;
216254 s. stack_lines . push ( "- Testing: pytest" . to_string ( ) ) ;
217255 }
218- if pyp. contains ( "ruff" ) {
256+ // Detect linting (ruff replaces flake8+isort+black)
257+ if pyp. contains ( "ruff" ) || root. join ( "ruff.toml" ) . exists ( ) {
219258 s. has_ruff = true ;
220- s. stack_lines . push ( "- Linting: Ruff" . to_string ( ) ) ;
259+ s. stack_lines . push ( "- Linting + Formatting : Ruff" . to_string ( ) ) ;
221260 }
222261 }
223262
@@ -265,24 +304,41 @@ fn generate_guard_commands(s: &DetectedStack) -> String {
265304 let mut typecheck = String :: new ( ) ;
266305 let mut format_check = String :: new ( ) ;
267306
307+ // Determine the JS/TS runner prefix (modern first: bun > pnpm > npx)
308+ let js_run = if s. has_bun { "bunx" } else if s. has_pnpm { "pnpm exec" } else { "npx" } ;
309+ // Determine the Python runner (modern first: uv > raw)
310+ let py_run = if s. has_uv { "uv run" } else { "" } ;
311+
268312 if s. has_rust {
269313 test = "cargo test --all" . to_string ( ) ;
270314 lint = "cargo clippy --all -- -D warnings" . to_string ( ) ;
271315 format_check = "cargo fmt --all -- --check" . to_string ( ) ;
272316 } else if s. has_python {
273- if s. has_pytest { test = "pytest" . to_string ( ) ; }
274- lint = if s. has_ruff { "ruff check ." . to_string ( ) } else { "flake8" . to_string ( ) } ;
317+ if s. has_pytest {
318+ test = if py_run. is_empty ( ) { "pytest" . to_string ( ) } else { format ! ( "{py_run} pytest" ) } ;
319+ }
320+ if s. has_ruff {
321+ lint = if py_run. is_empty ( ) { "ruff check ." . to_string ( ) } else { format ! ( "{py_run} ruff check ." ) } ;
322+ format_check = if py_run. is_empty ( ) { "ruff format --check ." . to_string ( ) } else { format ! ( "{py_run} ruff format --check ." ) } ;
323+ }
324+ // No flake8/black fallback — ruff is the modern standard
275325 } else if s. has_node {
276326 if s. has_vitest {
277- test = "npx vitest run". to_string ( ) ;
327+ test = format ! ( "{js_run} vitest run") ;
278328 } else if s. has_jest {
279- test = "npx jest" . to_string ( ) ;
329+ test = format ! ( "{js_run} vitest run" ) ; // recommend vitest even if jest detected
330+ }
331+ if s. has_biome {
332+ lint = format ! ( "{js_run} biome check ." ) ;
333+ format_check = format ! ( "{js_run} biome format --write=false ." ) ;
334+ } else if s. has_eslint {
335+ lint = format ! ( "{js_run} eslint ." ) ;
280336 }
281- if s. has_eslint { lint = "npx eslint ." . to_string ( ) ; }
282- if s. has_typescript { typecheck = "npx tsc --noEmit" . to_string ( ) ; }
337+ if s. has_typescript { typecheck = format ! ( "{js_run} tsc --noEmit" ) ; }
283338 } else if s. has_go {
284339 test = "go test ./..." . to_string ( ) ;
285340 lint = "golangci-lint run" . to_string ( ) ;
341+ format_check = "gofmt -l . | head -20" . to_string ( ) ;
286342 } else if s. has_ruby {
287343 if s. has_rails {
288344 test = "bundle exec rspec" . to_string ( ) ;
0 commit comments