Back to skills
SKILL.md
Env Setup
DSecuritySet up local dev environment — stack.yml, AGENTS.md Critical Rules, docs scaffolding, LSP. Triggered by /R-dev-init or standalone /R-env-setup. Triggers: "env setup" | "setup environment" | "scaffold rules".
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added September 23, 2026
Works with
Security analysis
56/100- Sends environment variables or credentials to an external URL
- Installs packages at runtime which could introduce malicious dependencies
- Installs packages at runtime which could introduce malicious dependencies
- Installs packages at runtime which could introduce malicious dependencies
- Installs packages at runtime which could introduce malicious dependencies
Pro scans all 2 files and shows the line behind each finding
npx -y skills add Roxabi/roxabi-plugins --skill env-setup --agent claude-codeAre you the author of Env Setup?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/roxabi-env-setup)---
name: R-env-setup
argument-hint: '[--force]'
description: 'Set up local dev environment — stack.yml, AGENTS.md Critical Rules, docs scaffolding, LSP. Triggered by /R-dev-init or standalone /R-env-setup. Triggers: "env setup" | "setup environment" | "scaffold rules".'
version: 0.1.0
allowed-tools: Bash, Read, Write, Edit, ToolSearch
---
# Env Setup
Let:
I_TS := `${CLAUDE_PLUGIN_ROOT}/skills/dev-init/init.ts`
Φ := CLAUDE_PLUGIN_ROOT
F := `--force` flag present in `$ARGUMENTS`
σ := `.dev/stack.yml`
D(label, result) := Display: `{label} {result}`
D✅(label) := D(label, "✅ Created")
D⏭(label) := D(label, "⏭ Skipped")
ensureGitignore(entry) := `grep -q '{entry}' .gitignore 2>/dev/null || echo '{entry}' >> .gitignore`
Configure local developer environment: stack config, governance rules, docs stubs, editor settings, LSP.
Runs standalone (`/R-env-setup`) or called by `/init` as part of full project initialization.
## Phase 1 — Stack Configuration
Set up σ early — later phases read runtime, package manager, commands, deploy platform, hooks tool, docs path.
1. `test -f .dev/stack.yml && echo exists || echo missing`
2. missing ∧ (`test -f .claude/stack.yml` ∨ `test -f .claude/dev-core.yml`) → **legacy layout** (contract predates `.dev/`) → Ask: **Migrate now** (recommended) | **Skip**.
- **Migrate** → O_stackMigrate: `mkdir -p .dev && git mv .claude/stack.yml .dev/ 2>/dev/null; git mv .claude/dev-core.yml .dev/ 2>/dev/null`
D("stack.yml", "✅ Migrated to .dev/ — commit alongside code"). **¬`cp` of the template**: the real contract already exists; copying the template over it would replace hand-tuned values (`release.model`, paths, commands) with generic defaults. Phase 1 complete — continue at Phase 1b.
- **Skip** → D⏭("stack.yml — contract left in .claude/, dev-core guards stay off"), skip to Phase 2.
3. missing ∧ ¬legacy → Ask: **Set up stack.yml now** (recommended) | **Skip** (fallback defaults).
4. **Set up** → O_stackSetup:
- `mkdir -p .dev && cp "${Φ}/stack.yml.example" .dev/stack.yml`
- Ask ∀ critical field: **Runtime** → bun|node|python → `runtime`+`package_manager` | **Backend path** (e.g. `apps/api`, blank=none) | **Frontend path** (e.g. `apps/web`, blank=none) | **Test command** → `commands.test`
- Write values into σ. Inform: "Fill in remaining fields in σ before running agents."
5. existing → D("stack.yml", "✅ Already exists"), skip.
Note: `.dev/stack.yml` is **committed** (project stack conventions — no secrets). Only `.env` is gitignored by dev-core. `.dev/dev-core.yml` contains only public GitHub Project node IDs and is committed.
### Phase 1b — Worktree-setup retrofit
For projects that already have σ but pre-date the worktree-setup hook. Detect the
gap and offer to scaffold `tools/worktree-setup.sh` + teardown. All steps are
inlined here — cross-skill prose references do not bind at runtime.
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
σ_has_hook := `grep -q 'worktree_setup:' .dev/stack.yml`
runtime_supported := σ.runtime ∈ {python, bun, node}
1. **σ missing** → D⏭("Worktree-setup retrofit — requires stack.yml"), skip.
2. **σ_has_hook true** → D("Worktree-setup retrofit", "✅ Already configured"), skip.
3. **runtime_supported false** → D⏭("Worktree-setup retrofit — runtime not in scope"), skip.
4. **Wider probe** — check for existing worktree-setup scripts under non-canonical names:
```bash
ALT_SCRIPT=$(grep -rl 'worktree[-_]setup' tools/*.sh scripts/*.sh 2>/dev/null | head -1)
```
ALT_SCRIPT ∃ ∧ ALT_SCRIPT ≠ "tools/worktree-setup.sh" →
D("Worktree-setup retrofit", "⚠️ Found existing worktree-setup script at ${ALT_SCRIPT} (not tools/worktree-setup.sh). Skipping scaffold — move it to tools/worktree-setup.sh or delete and re-run."), skip.
5. **test -f tools/worktree-setup.sh** ∧ ¬F → D("Worktree-setup retrofit", "⏭ Script present, σ key missing — fix σ only").
- Append `commands.worktree_setup: tools/worktree-setup.sh` + `commands.worktree_teardown: tools/worktree-teardown.sh` under `commands:` in σ.
- D✅("Worktree-setup retrofit — σ keys added"), skip remainder.
6. **Both absent** → present choice **Scaffold worktree-setup hook now** | **Skip**.
- **Skip** → D⏭("Worktree-setup retrofit"), continue.
- **Scaffold** → execute the following inline (verbatim duplication of stack-setup Phase 4b — required because cross-skill references do not bind at runtime):
a. Re-detect variables from σ and filesystem:
```bash
RUNTIME=$(grep '^runtime:' .dev/stack.yml | awk '{print $2}')
PM=$(grep '^package_manager:' .dev/stack.yml | awk '{print $2}')
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 "")
```
b. Build CTX_JSON safely via jq:
```bash
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)
}')
```
c. Preview:
```bash
IDS=$(bun "${TS}" list-selected --checklist "${CL}" --context-json "${CTX_JSON}")
COUNT=$(echo "$IDS" | awk -F, '{print NF}')
```
Echo: `Worktree-setup retrofit preview — ${RUNTIME}/${PM} · ${COUNT} concerns: ${IDS}`
COUNT == 0 → D⏭("Worktree-setup retrofit — no concerns matched"), skip.
d. user 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** → D⏭("Worktree-setup retrofit"), skip.
e. Write:
```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
```
f. Register keys in σ (idempotent — skip lines already present):
Append under `commands:` in `.dev/stack.yml`:
```yaml
worktree_setup: tools/worktree-setup.sh
worktree_teardown: tools/worktree-teardown.sh
```
g. D("Worktree-setup retrofit", "✅ Written (${COUNT} concerns: ${IDS}), σ keys registered").
Re-run idempotency: any subsequent `/R-env-setup` invocation re-evaluates the predicate — once σ has the key, step 2 short-circuits silently.
## Phase 2 — Scaffold AGENTS.md Critical Rules
Generate governance rules (dev process, decision protocol, git conventions, etc.) from σ values. Sections vary by project type.
σ ∄ → D("Critical Rules", "⏭ Skipped — requires stack.yml"), skip to Phase 3.
1. Run: `bun $I_TS scaffold-rules --stack-path .dev/stack.yml --agents-md AGENTS.md`
2. Parse JSON → extract `projectType`, `sections`, `markdown`, `existing`, `facts`.
3. Display:
```
Project type: {projectType}
Repo facts: baseBranch={facts.baseBranch} pm={facts.packageManager} .env.example={facts.hasEnvExample}
Parent AGENTS.md: {existing.parentPaths joined | "none"}
Parent imports: {existing.parentImports joined | "none"} ← machine-local; not auto-skip authority
Local sections: {existing.sectionIds or "none"}
Sections to scaffold: {sections.length} ({section ids})
```
> Parent context is **reporting only**. Silent auto-skip is forbidden — parent/ssot paths are often machine-local; committed AGENTS.md must stay portable for clones without that parent.
4. Present choice (always a gate — user is the gate):
- **Scaffold full** — all generated sections (portable governance for any clone)
- **Scaffold project-local only** — tldr + artifact-model + coding-standards + gotchas (when parent already loads fleet rules)
- **Merge** — append only section ids missing from `existing.sectionIds` (local titles only)
- **Skip**
When `existing.sectionIds` already covers all expected → still show table; default bias **Skip** / already complete.
5. Scaffold full / project-local / Replace → write `markdown` (for project-local: filter sections to ids ∈ {tldr, artifact-model, coding-standards, gotchas}, renumber). Preserve content before/after `## Critical Rules` if present.
6. Merge → ∀ section ∈ chosen set ∧ section.id ∉ existing.sectionIds → append after last existing Critical Rules heading.
7. D("Critical Rules", "✅ Scaffolded ({N} sections for {projectType}, base={facts.baseBranch})")
## Phase 3 — Documentation Scaffolding (Optional)
1. Read `docs.path` from σ (default: `docs`). Write format is always Markdown (`.md`).
2. `{docs.path}/standards/` ∃ → D("Docs scaffolding", "✅ Already present"), skip.
3. Ask: **Scaffold standard docs** (architecture/, standards/, guides/ with templates) | **Skip**.
4. yes:
```bash
bun "${CLAUDE_PLUGIN_ROOT}/skills/dev-init/init.ts" scaffold-docs --path <docs.path>
```
5. D("Docs scaffolding", "✅ Created {filesCreated.length} files in {docsPath}/").
## Phase 4 — LSP Support (Optional)
Enable `ENABLE_LSP_TOOL` for richer code intelligence in Claude Code sessions.
1. Read `lsp.enabled` from σ. `false` → D⏭("LSP — Disabled in stack.yml"), skip. `true` ∨ absent → continue.
2. `grep -q '^ENABLE_LSP_TOOL=' .env 2>/dev/null && echo "set" || echo "missing"`. set → D("ENABLE_LSP_TOOL", "✅ Already configured"), skip to step 6.
3. Ask: **Enable LSP** (`ENABLE_LSP_TOOL=1` + language server) | **Skip**.
4. yes:
a. Add to `.env` and `.env.example`:
```bash
echo 'ENABLE_LSP_TOOL=1' >> .env
grep -q '^ENABLE_LSP_TOOL=' .env.example 2>/dev/null || echo 'ENABLE_LSP_TOOL=1' >> .env.example
```
b. Detect LSP server from `lsp.server` or `runtime`:
| runtime | server | install | binary |
|---------|--------|---------|--------|
| `bun`/`node`/`deno` | typescript-language-server | bun: `bun add -d typescript-language-server typescript` / pnpm: `pnpm add -D ...` / npm: `npm install --save-dev ...` / yarn: `yarn add --dev ...` | `typescript-language-server` |
| `python` | pyright | `uv tool install pyright` or `pip install pyright` | `pyright` |
| `rust` | rust-analyzer | `rustup component add rust-analyzer` | `rust-analyzer` |
| `go` | gopls | `go install golang.org/x/tools/gopls@latest` | `gopls` |
c. `which <binary> 2>/dev/null`. missing → run install → re-check. still-missing → ⚠️ "not in PATH — restart shell".
d. **Claude Code LSP plugin** — detect from runtime:
| runtime | claude plugin name |
|---------|--------------------|
| `bun`/`node`/`deno` | `typescript-lsp` |
| `python` | `pyright-lsp` |
| `rust`/`go` | (none — skip) |
`claude plugin list 2>/dev/null | grep -q '<plugin-name>'` → installed → skip.
¬installed → Ask: **Global** (recommended for solo) | **Project** (commits to `.claude/settings.json`) | **Skip**.
- Global: `claude plugin install <plugin-name>`
- Project: `claude plugin install <plugin-name> --scope project`
e. D("LSP", "✅ ENABLE_LSP_TOOL=1 set, <server> installed, <plugin-name> plugin active").
5. Skip → D⏭("LSP").
6. Already set ∧ binary ∃ → check Claude Code plugin (step 4d). D("LSP", "✅ Already configured (<binary>[, plugin missing → run fix])").
## Phase 5 — Report
```
Env Setup Complete
==================
stack.yml ✅ Configured / ✅ Already exists / ⏭ Skipped
Critical Rules ✅ Scaffolded (N sections) / ✅ Already complete / ⏭ Skipped
Docs scaffolding ✅ Created N files / ✅ Already present / ⏭ Skipped
LSP ✅ Configured / ✅ Already set / ⏭ Disabled / ⏭ Skipped
Worktree-setup ✅ Scaffolded / ✅ Already configured / ⏭ Skipped
Next: run /R-seed-docs to populate docs stubs. Issue triage (labels, blocked-by, parent/child) lives in the companion **issue-triage** plugin.
```
## Safety Rules
1. **Never overwrite existing `.dev/stack.yml` values** without F or explicit confirmation
2. **Always present choices and wait for user reply** before any write operation
3. **Commit `.dev/stack.yml`** — it holds project stack conventions, not secrets. Gitignore `.env` only.
4. **Idempotent** — skip already-configured items unless F
$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.