Back to skills
SKILL.md
Stack Setup
BSecurityFill `.dev/stack.yml` via guided discovery.
- 9 stars
- 0 votes
- 0 copies
- 1 view
- Added September 23, 2026
Works with
Security analysis
88/100- Sends environment variables or credentials to an external URL
Pro scans all 2 files and shows the line behind each finding
npx -y skills add Roxabi/roxabi-plugins --skill stack-setup --agent claude-codeAre you the author of Stack Setup?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/roxabi-stack-setup)---
name: R-stack-setup
disable-model-invocation: true
argument-hint: '[--force]'
description: Fill `.dev/stack.yml` via guided discovery.
version: 0.3.2
allowed-tools: Read, Edit, Write, Bash, Glob, ToolSearch
---
# Stack Setup Wizard
Auto-discover project config → confirm → write `.dev/stack.yml`. Safe to re-run.
Let: σ := `.dev/stack.yml` | π := proposed config table
## Phase 0 — Idempotency
`test -f .dev/stack.yml && echo exists || echo missing`
σ ∃ ∧ `--force` ∉ `$ARGUMENTS` → present choice **Re-configure** | **Skip**
→ Skip: "Keeping existing σ. Run with `--force` to reconfigure."
σ ∄ ∧ (`test -f .claude/stack.yml` ∨ `test -f .claude/dev-core.yml`) → **legacy layout** (contract predates `.dev/`) → present choice **Migrate now** (recommended) | **Skip**
→ Migrate: `mkdir -p .dev && git mv .claude/stack.yml .dev/ 2>/dev/null; git mv .claude/dev-core.yml .dev/ 2>/dev/null` → "Contract migrated to .dev/ — commit alongside code." σ now ∃ → re-enter the σ ∃ branch (**Re-configure** | **Skip**); ¬overwrite the migrated values without `--force`.
→ Skip: abort — "Contract left in .claude/. Writing a fresh σ would orphan it; migrate first."
σ ∄ ∧ ¬legacy → `mkdir -p .dev`
## Phase 1 — Check /init prerequisite
`test -f .dev/dev-core.yml && echo done || echo missing`
`missing` → present choice **Continue anyway** | **Abort (run /init first)**
## Phase 2 — Auto-discover
Run all detection; derive π:
```bash
# Runtime & package manager
echo "--- RUNTIME ---"
if [ -f pyproject.toml ]; then
grep -q '\[tool\.uv\]' pyproject.toml && echo "runtime=python pm=uv" || echo "runtime=python pm=pip"
elif [ -f bun.lockb ]; then echo "runtime=bun pm=bun"
elif [ -f pnpm-lock.yaml ]; then echo "runtime=node pm=pnpm"
elif [ -f yarn.lock ]; then echo "runtime=node pm=yarn"
elif [ -f package.json ]; then echo "runtime=node pm=npm"
else echo "runtime=unknown pm=unknown"
fi
# Source / backend path
echo "--- SOURCE PATH ---"
grep -oP '(?<=packages = \[")[^"]+' pyproject.toml 2>/dev/null | head -1 \
|| (test -d src && echo "src") \
|| (test -d apps/api && echo "apps/api") \
|| echo ""
# Backend framework
echo "--- BACKEND FRAMEWORK ---"
grep -iE '\btyper\b|\bfastapi\b|\bflask\b|\bdjango\b' pyproject.toml 2>/dev/null | head -1 \
|| grep -oE '"@nestjs/core"|"express"|"fastify"' package.json 2>/dev/null | head -1 \
|| echo "none"
# Frontend framework
echo "--- FRONTEND FRAMEWORK ---"
grep -oE '"next"|"@sveltejs/kit"|"@remix-run/react"|"nuxt"|"@tanstack/start"' package.json 2>/dev/null | head -1 \
|| echo "none"
# ORM
echo "--- ORM ---"
grep -oE '"drizzle-orm"|"@prisma/client"|"@prisma/client"|"mongoose"|"typeorm"' package.json 2>/dev/null | head -1 \
|| echo "none"
# Test framework
echo "--- TESTING ---"
grep -q 'pytest' pyproject.toml 2>/dev/null && echo "pytest" \
|| grep -oE '"vitest"|"jest"' package.json 2>/dev/null | head -1 \
|| echo "none"
# Linter / formatter
echo "--- LINTER ---"
grep -q '\[tool\.ruff\]' pyproject.toml 2>/dev/null && echo "ruff config=pyproject.toml" \
|| (test -f biome.json && echo "biome config=biome.json") \
|| grep -q '"eslint"' package.json 2>/dev/null && echo "eslint config=.eslintrc.*" \
|| echo "none"
# Build orchestrator
echo "--- ORCHESTRATOR ---"
(test -f turbo.jsonc && echo "turbo config=turbo.jsonc") \
|| (test -f turbo.json && echo "turbo config=turbo.json") \
|| (test -f nx.json && echo "nx config=nx.json") \
|| echo "none"
# Docs
echo "--- DOCS ---"
test -d docs && echo "path=docs format=md" || echo "none"
# Project scripts (entry points)
echo "--- PROJECT SCRIPTS ---"
grep -A5 '^\[project\.scripts\]' pyproject.toml 2>/dev/null | grep -v '^\[' | head -5 \
|| (grep '"scripts"' package.json 2>/dev/null && python3 -c "import json,sys; pkg=json.load(open('package.json')); [print(f'{k}={v}') for k,v in (pkg.get('scripts') or {}).items() if k in ['dev','build','test','lint','typecheck','format']]" 2>/dev/null) \
|| echo ""
```
**Runtime/PM → prefix:** `uv` → python/uv/`uv run` | `bun` → bun/bun/`bun run` | `npm/pnpm/yarn` → node/{pm}/`{pm} run`
**Commands by runtime:**
- Python/uv: `dev: uv run <first-script>` | `test: uv run pytest` | `lint: uv run ruff check .` | `format: uv run ruff format .` | `typecheck: uv run ruff check --select=PYI .` | `install: uv sync`
- Node/Bun: `dev/test/lint/format/typecheck: {pm} run <key>` | `install: {pm} install`
**Formatter fix:** ruff → `uv run ruff format . && uv run ruff check --fix .` | biome → `bunx biome check --write` | eslint → `npx eslint --fix .`
**Mixed-stack monorepos** (JS/TS + Python): `formatters:` array instead of `formatter_fix_cmd`:
```yaml
build:
formatters:
- cmd: "bunx biome check --write"
ext: [".ts", ".tsx", ".js", ".jsx", ".json"]
- cmd: "ruff format"
ext: [".py"]
```
∀ formatter receives only matching `ext` files. `formatter_fix_cmd` = fallback for single-formatter projects.
**Standards paths** — `docs/` ∃ → include: backend, testing, code_review, architecture, configuration, contributing.
## Phase 3 — Confirm
Display π:
```
Detected configuration
======================
Runtime: {runtime} / {pm}
Backend: {framework} at {path}
Frontend: {frontend_framework | "none"}
ORM: {orm | "none"}
Testing: {test_framework | "none"}
Linter: {formatter} ({formatter_config})
Orchestrator:{orchestrator | "none"}
Docs: {docs_path | "none"}
Commands:
dev: {dev_cmd}
test: {test_cmd}
lint: {lint_cmd}
format: {format_cmd}
typecheck: {typecheck_cmd}
install: {install_cmd}
```
→ present choice **Looks good — write it** | **Edit a field** | **Abort**
"Edit a field" → ask which + new value; apply; re-display π. Repeat until confirmed.
## Phase 4 — Write stack.yml
Assemble σ. Omit `none`/empty keys entirely. Ensure the directory exists first: `mkdir -p .dev`.
```yaml
# .dev/stack.yml — dev-core stack configuration
# Commit this file with the project. Secrets live in .env only.
schema_version: "1.0"
runtime: {RUNTIME}
package_manager: {PM}
backend:
framework: {BE_FRAMEWORK}
path: {BE_PATH}
# orm: {BE_ORM} (only if orm != none)
# frontend: (only if frontend framework detected)
# framework: {FE_FRAMEWORK}
# path: {FE_PATH}
# shared: (only if monorepo detected)
# types: packages/types
# ui: packages/ui
# config: packages/config
build:
# orchestrator: {ORCHESTRATOR} (only if detected)
# orchestrator_config: {ORCHESTRATOR_CONFIG}
formatter: {FORMATTER}
formatter_config: {FORMATTER_CONFIG}
formatter_fix_cmd: "{FORMATTER_FIX_CMD}" # single formatter; use formatters[] for mixed stacks
# formatters: # mixed-stack alternative (FE JS + BE Python etc.)
# - cmd: "bunx biome check --write"
# ext: [".ts", ".tsx", ".js", ".jsx", ".json"]
# - cmd: "ruff format"
# ext: [".py"]
testing:
unit: {UNIT_TEST}
# e2e: (only if playwright/cypress detected)
# quality_gates: (python/node/bun — opt-in code-hygiene guards; omit section entirely when runtime not supported)
# file_length:
# enabled: true
# max_lines: 300
# globs: ["src/**/*.py"]
# exemptions_file: tools/file_exemptions.txt
# # metric: raw # optional — "raw" (default, wc -l) or "sloc" (excludes blanks/comments/docstrings)
# # sloc requires radon (python) or node+npx (node); see /R-release-setup N4b
# folder_size:
# enabled: true
# max_files: 12
# globs: ["src/**"]
# exemptions_file: tools/folder_exemptions.txt
# import_layers:
# enabled: true
# stage: pre-push
# config: .importlinter
deploy:
platform: {DEPLOY_PLATFORM} # none | vercel | cloudflare | cloudflare-pages
# ci: (optional — merge strategy for private free-plan repos)
# merge: merge-on-green
docs:
framework: none
path: {DOCS_PATH}
format: md
commands:
dev: {DEV_CMD}
test: {TEST_CMD}
lint: {LINT_CMD}
format: {FORMAT_CMD}
typecheck: {TYPECHECK_CMD}
install: {INSTALL_CMD}
artifacts:
brainstorms: artifacts/brainstorms
analyses: artifacts/analyses
specs: artifacts/specs
frames: artifacts/frames
plans: artifacts/plans
# standards: (only if docs/ exists on disk)
# backend: {docs}/standards/backend-patterns.md
# testing: {docs}/standards/testing.md
# code_review: {docs}/standards/code-review.md
# architecture: {docs}/architecture/
# configuration: {docs}/standards/configuration.md
# contributing: {docs}/contributing.md
```
**Conditional rendering rules for the template above:**
- `frontend:` — include (uncommented) only if a frontend framework was detected; otherwise omit entirely.
- `shared:` — include only if a monorepo layout was detected; otherwise omit entirely.
- `quality_gates:` — include (uncommented) only if `runtime ∈ {python, node, bun}`; otherwise omit the section entirely. For `runtime: node` or `runtime: bun`, include only the `file_length` sub-block (folder_size and import_layers are Python-only). For bun, set `globs: ["src/**/*.{ts,tsx,js,jsx}"]`.
- `deploy.platform` — Ask in Phase 3 if not auto-detected: **none** | **vercel** | **cloudflare** | **cloudflare-pages** (maps to Cloudflare deploy workflow).
- `ci.merge` — optional; set `merge-on-green` for private free-plan repos where native auto-merge is unavailable (default: omit → auto-merge).
## Phase 4b — Worktree-setup scaffold
Compose `tools/worktree-setup.sh` + `tools/worktree-teardown.sh` from the checklist
and register both hooks in σ. Skipped for unsupported runtimes.
Let:
WS := tools/worktree-setup.sh
WT := tools/worktree-teardown.sh
CL := ${CLAUDE_PLUGIN_ROOT}/references/worktree-setup-checklist.md
TS := ${CLAUDE_PLUGIN_ROOT}/scripts/worktreeScaffold.ts
CTX := ProjectContext built from Phase 2 detection
1. **Runtime gate.** runtime ∉ {python, bun, node} → D⏭("Worktree-setup scaffold — runtime not supported"), skip phase.
2. **Existence check.** `test -f tools/worktree-setup.sh` →
- ∃ ∧ ¬`--force` → D("Worktree-setup scaffold", "⏭ Already present"), skip.
- ∃ ∧ `--force` → present choice **Regenerate** | **Keep existing** | **Abort**. "Keep" / "Abort" → skip / abort wizard.
3. **Build ProjectContext.** Phase 2 output was display-only; re-detect here to populate shell variables for jq:
```bash
# Re-detect from filesystem (Phase 2 echo output is display-only, not shell assignments)
if [ -f pyproject.toml ]; then
grep -q '\[tool\.uv\]' pyproject.toml && RUNTIME=python && PM=uv || RUNTIME=python && PM=pip
elif [ -f bun.lockb ]; then RUNTIME=bun; PM=bun
elif [ -f pnpm-lock.yaml ]; then RUNTIME=node; PM=pnpm
elif [ -f yarn.lock ]; then RUNTIME=node; PM=yarn
elif [ -f package.json ]; then RUNTIME=node; PM=npm
else RUNTIME=unknown; PM=unknown
fi
MONOREPO_BOOL=$([ -f turbo.jsonc ] || [ -f turbo.json ] || [ -f nx.json ] && echo true || echo false)
HOOKS_TOOL=$([ -f lefthook.yml ] || [ -f .lefthook.yml ] && echo lefthook \
|| ([ -f .pre-commit-config.yaml ] && echo pre-commit) || echo none)
DATABASE=$(grep -lE 'NEON_DATABASE_URL|@neondatabase' .env.example apps/api/package.json 2>/dev/null \
| head -1 > /dev/null && echo neon || echo none)
BE_PATH=$(grep -oP '(?<=packages = \[")[^"]+' pyproject.toml 2>/dev/null | head -1 \
|| (test -d apps/api && echo apps/api) || echo "")
ENV_FILES=$(ls .env.example .env.local 2>/dev/null | head -3 | tr '\n' ' ' || echo "")
# Build CTX_JSON safely via jq (no heredoc injection risk)
command -v jq > /dev/null 2>&1 || { echo "jq not found — install jq to continue"; exit 1; }
CTX_JSON=$(jq -n \
--arg runtime "$RUNTIME" \
--arg pm "$PM" \
--argjson monorepo "${MONOREPO_BOOL}" \
--arg hooks_tool "${HOOKS_TOOL:-lefthook}" \
--arg database "${DATABASE:-none}" \
--arg be_path "${BE_PATH:-}" \
--arg env_files "${ENV_FILES:-}" \
'{
runtime: $runtime,
package_manager: $pm,
monorepo: $monorepo,
hooks_tool: $hooks_tool,
env_files: ($env_files | split(" ") | map(select(. != ""))),
database: $database,
backend_paths: ($be_path | if . == "" then [] else [.] end)
}')
```
4. **Preview.** Run:
```bash
IDS=$(bun "${TS}" list-selected --checklist "${CL}" --context-json "${CTX_JSON}")
COUNT=$(echo "$IDS" | awk -F, '{print NF}')
```
Echo: `Worktree-setup scaffold preview — {RUNTIME}/{PM} · ${COUNT} concerns: ${IDS}`
${COUNT} == 0 → D⏭("Worktree-setup scaffold — no concerns matched"), skip.
5. **Confirm.** Options depend on whether WS already exists:
- WS ∃ (Regenerate path) → present choice **Write scripts** | **Show diff** | **Abort**.
- **Show diff** → `diff -u <(bun "${TS}" compose --checklist "${CL}" --context-json "${CTX_JSON}" --mode setup) tools/worktree-setup.sh | head -80` then re-present user choice **Write scripts** | **Abort**.
- WS ∄ (fresh-install path) → present choice **Write scripts** | **Preview composed body** | **Abort**.
- **Preview composed body** → `bun "${TS}" compose --checklist "${CL}" --context-json "${CTX_JSON}" --mode setup | head -80` then re-present user choice **Write scripts** | **Abort**.
- **Abort** (either path) → D⏭("Worktree-setup scaffold"), skip.
6. **Write scripts.**
```bash
mkdir -p tools
bun "${TS}" compose --checklist "${CL}" --context-json "${CTX_JSON}" --mode setup > tools/worktree-setup.sh
bun "${TS}" compose --checklist "${CL}" --context-json "${CTX_JSON}" --mode teardown > tools/worktree-teardown.sh
chmod +x tools/worktree-setup.sh tools/worktree-teardown.sh
```
7. **Register keys in σ.** Append under `commands:` block of `.dev/stack.yml`:
```yaml
worktree_setup: tools/worktree-setup.sh
worktree_teardown: tools/worktree-teardown.sh
```
Idempotent: skip lines already present.
8. **Report.**
```
Worktree-setup scaffold
tools/worktree-setup.sh ✅ Written (${COUNT} concerns: ${IDS})
tools/worktree-teardown.sh ✅ Written
commands.worktree_setup ✅ Registered in σ
commands.worktree_teardown ✅ Registered in σ
```
Note: `.dev/stack.yml` itself is committed (project stack conventions — no secrets). Only `.env` is gitignored by dev-core. `.dev/dev-core.yml` contains only public repo configuration and is committed.
## Phase 5 — Summary
```
Stack configuration written
===========================
Runtime: {RUNTIME} / {PM}
Backend: {BE_FRAMEWORK} at {BE_PATH}
Frontend: {FE_FRAMEWORK | "none"}
Testing: {UNIT_TEST}
Linter: {FORMATTER} ({FORMATTER_CONFIG})
Docs: {DOCS_PATH | "none"}
.dev/stack.yml ✅ Written
.gitignore ✅ Updated / Already set
tools/worktree-setup.sh ✅ Written / Skipped (unsupported runtime | already present)
tools/worktree-teardown.sh ✅ Written / Skipped (unsupported runtime | already present)
commands.worktree_setup ✅ Registered in σ / Skipped
commands.worktree_teardown ✅ Registered in σ / Skipped
⚠️ Missing standards docs: (list any configured paths that don't exist on disk)
Next:
/R-dev-checkup Verify all checks pass
/R-dev #N Start working on an issue
```
$ARGUMENTS
Files in this skill
- README.md
- SKILL.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.