# R-env-setup

> Set up local dev environment — stack.yml, CLAUDE.md Critical Rules, docs scaffolding, LSP. Triggered by /R-dev-init or standalone /R-env-setup. Triggers: "env setup" | "setup environment" | "scaffold rules".

- Skill: `roxabi/r-env-setup` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add roxabi/r-env-setup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/roxabi/r-env-setup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Roxabi (https://skillmd.com/u/roxabi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/roxabi/r-env-setup

---


# 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; git mv .claude/stack.yml.example .dev/ 2>/dev/null`
     D("stack.yml", "✅ Migrated to .dev/ — commit alongside code"). **¬`cp` of the template**: the real contract already exists; copying `stack.yml.example` over it would replace hand-tuned values (`release.model`, paths, commands) with generic defaults. Continue at step 5.
   - **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. ¬`.dev/stack.yml.example` → `mkdir -p .dev && cp "${Φ}/stack.yml.example" .dev/stack.yml.example`. D("stack.yml.example", "✅ Created (reference template)").
6. 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}/tools/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 CLAUDE.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 --claude-md CLAUDE.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 CLAUDE.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 CLAUDE.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

