Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit f131d32

Browse files
z23ccclaude
andcommitted
feat: modern toolchain detection — uv/bun/pnpm/ruff/biome preferred
init.rs detect_stack now prefers modern tools: - Python: uv > pip (detects uv.lock, .python-version) - Node: bun > pnpm > npm (detects bun.lockb, pnpm-lock.yaml) - Python lint: ruff > (no flake8 fallback) — ruff replaces flake8+isort+black - Python format: ruff format > black - JS lint: biome > eslint (detects biome.json) - JS test: vitest recommended even when jest detected Guard commands use modern runners: - bunx/pnpm exec/npx (auto-detected from lockfile) - uv run pytest/ruff (when uv.lock present) - No more flake8/black in generated commands Django skill: pip install → uv sync, black → ruff format Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e5e158f commit f131d32

3 files changed

Lines changed: 84 additions & 29 deletions

File tree

bin/flowctl

0 Bytes
Binary file not shown.

flowctl/crates/flowctl-cli/src/commands/admin/init.rs

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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();

skills/flow-code-django/references/verification.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ pip list --outdated
1313
## Phase 2: Code Quality
1414

1515
```bash
16-
mypy . --config-file pyproject.toml
17-
ruff check . --fix
18-
black . --check
19-
isort . --check-only
20-
python manage.py check --deploy
16+
uv run mypy . --config-file pyproject.toml
17+
uv run ruff check . --fix
18+
uv run ruff format --check .
19+
uv run python manage.py check --deploy
2120
```
2221

2322
## Phase 3: Migrations
@@ -148,8 +147,8 @@ jobs:
148147

149148
- name: Install
150149
run: |
151-
pip install -r requirements.txt
152-
pip install ruff black mypy pytest pytest-django pytest-cov bandit pip-audit
150+
uv sync
151+
uv pip install ruff mypy pytest pytest-django pytest-cov bandit
153152
154153
- name: Code quality
155154
run: |
@@ -177,7 +176,7 @@ jobs:
177176
|-------|---------|
178177
| Type check | `mypy .` |
179178
| Lint | `ruff check .` |
180-
| Format | `black . --check` |
179+
| Format | `ruff format --check .` |
181180
| Migrations | `python manage.py makemigrations --check` |
182181
| Tests | `pytest --cov=apps` |
183182
| Security | `pip-audit && bandit -r .` |

0 commit comments

Comments
 (0)