agentsclimarketplace

Vibe doctor

Skill aakashdhar/vibe-skill/vibe-doctor

vibe-* is a collection of Claude Code CLI skills. Each skill is a structured instruction set that tells Claude Code exactly what to do at a specific moment in a project — how to plan it, build it, review it, test it, deploy it, and hand it off.

Install
npx -y skills add aakashdhar/vibe-skill --skill vibe-doctor

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Environment health check and auto-remediation for vibe-* projects. Checks node_modules vs package.json drift, nested .git directories, Tailwind and shadcn version mismatches, dotenv load order, Vite HMR WebSocket config, husky hook permissions, ESLint config validity, TypeScript path resolution, and missing dependencies in generated code. Auto-remediates where safe. Flags where human action is needed. Outputs a health report and writes vibe/.doctor-last-run for vibe-progress. Triggers on "doctor:", "check environment", "fix my environment", "env check", "why won't my app start", "something is broken at startup", "app won't start". Also invoked by vibe-fix-bug when an environment bug is detected in triage. Run manually or via PreToolUse hook.

SKILL.md

14.0 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it

Vibe Doctor Skill

Environment health check and auto-remediation. Catches the failure modes that compound across sessions before they waste build time. Runs in 20-40 seconds. Auto-fixes what it can. Flags what it cannot.

Always run in Plan Mode for diagnosis. Auto-exits to execute remediations.


Why this skill exists

The most common session-wasting pattern in vibe-* projects: session starts → command fails → agent investigates → discovers env problem → spends 20 minutes fixing config → never gets to the actual work.

These failures are all checkable in 30 seconds:

  • node_modules missing or stale (package.json changed since last install)
  • Tailwind v3 project running v4 syntax from generated code
  • dotenv loaded after code that reads process.env
  • Vite HMR WebSocket not configured for dev server
  • husky hooks not executable (no chmod)
  • Nested .git directory breaking git operations
  • TypeScript paths not matching folder structure
  • Missing package referenced in generated import

Check once at session start. Fix everything. Build with confidence.


Setting up the PreToolUse hook (recommended)

After running doctor: for the first time, it offers to install itself as a hook. Or add manually to .claude/settings.json at project root:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "[ -f vibe/.doctor-last-run ] && [ $(( $(date +%s) - $(cat vibe/.doctor-last-run 2>/dev/null || echo 0) )) -lt 3600 ] || echo 'DOCTOR: Run doctor: to check environment health'"
      }]
    }]
  }
}

This surfaces a reminder if doctor: hasn't run in the last hour. For full auto-run on every session, vibe-mode: autonomous enables this automatically.


Step 0 — Pre-flight

# Detect project type
ls package.json 2>/dev/null && echo "NODE=true" || echo "NODE=false"
ls pyproject.toml 2>/dev/null || ls requirements.txt 2>/dev/null && echo "PYTHON=true" || echo "PYTHON=false"
ls pubspec.yaml 2>/dev/null && echo "FLUTTER=true" || echo "FLUTTER=false"
ls vibe/ 2>/dev/null && echo "VIBE=true" || echo "VIBE=false"

Read references/CHECKS.md for the full check implementations.


Step 1 — Run all checks

Run all checks in parallel where possible. Collect results. Do not stop on first failure — run everything, report all at once.

Check 1 — node_modules integrity

# Check if node_modules exists
if [ ! -d "node_modules" ]; then
  echo "FAIL:node_modules missing"
  exit 0
fi

# Check if package.json has changed since last install
if [ package.json -nt node_modules ]; then
  echo "FAIL:package.json newer than node_modules — run npm install"
else
  echo "PASS:node_modules"
fi

# Check for packages in package.json not in node_modules
node -e "
const pkg = require('./package.json');
const deps = {...(pkg.dependencies||{}), ...(pkg.devDependencies||{})};
const missing = Object.keys(deps).filter(d => {
  try { require.resolve(d, {paths: [process.cwd()]}); return false; }
  catch { return true; }
});
if (missing.length) console.log('FAIL:missing packages: ' + missing.join(', '));
else console.log('PASS:all packages resolvable');
" 2>/dev/null || echo "SKIP:node check requires node"

Check 2 — Nested .git directories

# Find nested .git dirs (common cause of submodule confusion)
nested=$(find . -name ".git" -not -path "./.git" -not -path "*/node_modules/*" -maxdepth 4 2>/dev/null)
if [ -n "$nested" ]; then
  echo "FAIL:nested .git found: $nested"
else
  echo "PASS:no nested .git"
fi

Check 3 — Tailwind version consistency

if [ -f "tailwind.config.js" ] || [ -f "tailwind.config.ts" ]; then
  # Get installed version
  tw_version=$(node -e "console.log(require('./node_modules/tailwindcss/package.json').version)" 2>/dev/null)
  
  if [ -z "$tw_version" ]; then
    echo "SKIP:tailwind not installed"
  else
    major=$(echo $tw_version | cut -d. -f1)
    
    # Check for v4 syntax in a v3 project
    if [ "$major" = "3" ]; then
      v4_syntax=$(grep -r "@import \"tailwindcss\"" src/ 2>/dev/null | head -3)
      if [ -n "$v4_syntax" ]; then
        echo "FAIL:Tailwind v3 installed but v4 import syntax found in src/"
      else
        echo "PASS:Tailwind v$tw_version consistent"
      fi
    fi
    
    # Check for v3 config in a v4 project
    if [ "$major" = "4" ]; then
      v3_config=$(grep -l "module.exports" tailwind.config* 2>/dev/null)
      if [ -n "$v3_config" ]; then
        echo "FAIL:Tailwind v4 installed but v3 config syntax (module.exports) found"
      else
        echo "PASS:Tailwind v$tw_version consistent"
      fi
    fi
  fi
else
  echo "SKIP:no tailwind config"
fi

Check 4 — dotenv load order

if [ -f ".env" ] || [ -f ".env.local" ]; then
  # Find entry point
  entry=$(cat package.json 2>/dev/null | node -e "
    const p = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
    const main = p.main || (p.scripts && p.scripts.start && p.scripts.start.split(' ').pop());
    console.log(main || 'src/index.ts');
  " 2>/dev/null)
  
  if [ -n "$entry" ] && [ -f "$entry" ]; then
    # Check if dotenv is configured before any process.env usage
    dotenv_line=$(grep -n "dotenv\|config()" "$entry" 2>/dev/null | head -1 | cut -d: -f1)
    env_line=$(grep -n "process\.env\." "$entry" 2>/dev/null | head -1 | cut -d: -f1)
    
    if [ -n "$dotenv_line" ] && [ -n "$env_line" ]; then
      if [ "$dotenv_line" -gt "$env_line" ]; then
        echo "FAIL:dotenv loaded at line $dotenv_line but process.env used at line $env_line in $entry"
      else
        echo "PASS:dotenv loads before process.env usage"
      fi
    else
      echo "SKIP:could not determine dotenv order"
    fi
  fi
else
  echo "SKIP:no .env file"
fi

Check 5 — Vite HMR WebSocket

if [ -f "vite.config.ts" ] || [ -f "vite.config.js" ]; then
  # Check if server config exists with HMR settings
  has_server=$(grep -l "server:" vite.config* 2>/dev/null)
  if [ -n "$has_server" ]; then
    has_hmr=$(grep "hmr:" vite.config* 2>/dev/null)
    if [ -z "$has_hmr" ]; then
      echo "WARN:Vite server config exists but no explicit HMR config — WebSocket issues possible in some environments"
    else
      echo "PASS:Vite HMR configured"
    fi
  else
    echo "INFO:Vite config has no server section — default HMR applies"
  fi
else
  echo "SKIP:no vite config"
fi

Check 6 — Husky hook permissions

if [ -d ".husky" ]; then
  non_exec=$(find .husky -type f ! -name "_" ! -name "*.sample" ! -executable 2>/dev/null)
  if [ -n "$non_exec" ]; then
    echo "FAIL:husky hooks not executable: $non_exec"
  else
    echo "PASS:husky hooks executable"
  fi
else
  echo "SKIP:no .husky directory"
fi

Check 7 — TypeScript path resolution

if [ -f "tsconfig.json" ]; then
  # Check for path aliases that don't match actual folders
  paths=$(node -e "
    const ts = JSON.parse(require('fs').readFileSync('tsconfig.json','utf8').replace(/\/\/.*/g,''));
    const paths = (ts.compilerOptions && ts.compilerOptions.paths) || {};
    const baseUrl = (ts.compilerOptions && ts.compilerOptions.baseUrl) || '.';
    Object.entries(paths).forEach(([alias, targets]) => {
      targets.forEach(target => {
        const resolved = target.replace('*', '').replace(/\/$/, '');
        const fs = require('fs');
        if (!fs.existsSync(resolved)) {
          console.log('FAIL:tsconfig path alias ' + alias + ' points to missing folder: ' + resolved);
        }
      });
    });
    console.log('PASS:tsconfig paths resolved');
  " 2>/dev/null)
  echo "$paths" | head -5
else
  echo "SKIP:no tsconfig.json"
fi

Check 8 — ESLint config validity

if ls eslint.config* .eslintrc* 2>/dev/null | head -1 | grep -q .; then
  # Try parsing the config
  result=$(npx eslint --print-config src/index.ts 2>&1 | head -3)
  if echo "$result" | grep -qi "error\|cannot find\|invalid"; then
    echo "FAIL:ESLint config error: $(echo $result | head -c 100)"
  else
    echo "PASS:ESLint config valid"
  fi
else
  echo "SKIP:no ESLint config"
fi

Check 9 — Git state

# Check git is initialised
git status 2>/dev/null || { echo "WARN:not a git repository"; exit 0; }

# Check for uncommitted changes that might affect build
staged=$(git diff --staged --name-only 2>/dev/null | wc -l)
if [ "$staged" -gt 20 ]; then
  echo "WARN:$staged staged files — consider committing before long session"
else
  echo "PASS:git state clean"
fi

# Check current branch
branch=$(git branch --show-current 2>/dev/null)
echo "INFO:on branch $branch"

Check 10 — vibe/ folder integrity (if vibe project)

if [ -d "vibe" ]; then
  missing=""
  [ ! -f "vibe/TASKS.md" ] && missing="$missing TASKS.md"
  [ ! -f "vibe/SPEC.md" ] && missing="$missing SPEC.md"
  [ ! -f "CLAUDE.md" ] && missing="$missing CLAUDE.md(root)"
  
  if [ -n "$missing" ]; then
    echo "WARN:vibe/ exists but missing:$missing"
  else
    echo "PASS:vibe/ folder complete"
  fi
else
  echo "INFO:not a vibe project (no vibe/ folder)"
fi

Step 2 — Triage results

Categorise each result:

PASS    — no action needed
FAIL    — auto-remediate if safe, flag if not
WARN    — surface to user, suggest fix
INFO    — informational, no action
SKIP    — check not applicable to this project

Auto-remediation safe list (fix without asking):

  • node_modules missing → npm install
  • husky hooks not executable → chmod +x .husky/*
  • node_modules stale → npm install

Flag for human action (show exact command, do not run):

  • Tailwind version mismatch → show the specific file and line
  • dotenv order → show the two lines that need swapping
  • Nested .git → show git rm --cached [path] && rm -rf [path]
  • TypeScript path alias → show which folder needs creating
  • ESLint config → show the parse error

Step 3 — Auto-remediate safe fixes

For each FAIL with safe remediation:

# node_modules missing or stale
npm install

# husky hooks not executable
chmod +x .husky/*
git config core.hooksPath .husky

# Vite HMR — add config if missing and project uses Railway/Nixpacks
# (only if package.json has a Railway-style deploy config)

After each remediation — verify it worked:

# Re-run the specific check that failed
# Report: "Fixed: [what was fixed]"

Step 4 — Output health report

DOCTOR — [project name] — [date] [time]
────────────────────────────────────────────
✅ node_modules      — all packages installed and resolvable
✅ .git structure    — no nested repositories
⚠️  Vite HMR         — no explicit HMR config (may cause issues on Railway)
   Fix: add server.hmr config to vite.config.ts
✅ dotenv order      — loaded before process.env usage
✅ husky hooks       — all executable
✅ TypeScript paths  — all aliases resolve
✅ ESLint config     — valid
✅ Tailwind          — v3, consistent syntax
✅ git state         — clean, on branch main
✅ vibe/ folder      — TASKS.md, SPEC.md, CLAUDE.md present

AUTO-FIXED:
  ✅ husky hooks — chmod +x applied to 3 hooks

NEEDS ATTENTION:
  ⚠️  [description] — [exact command to fix]

ENVIRONMENT: [HEALTHY / WARNINGS / NEEDS FIXES]
────────────────────────────────────────────

Step 5 — Write .doctor-last-run

mkdir -p vibe
date +%s > vibe/.doctor-last-run

This allows vibe-progress to show env health status in the dashboard and the PreToolUse hook to know when doctor last ran.


Step 6 — Offer hook installation

If .claude/settings.json does not already have a doctor hook:

"Doctor complete. Want me to add a session-start reminder hook to .claude/settings.json so this runs automatically? (y/n)"

If yes — append to .claude/settings.json (creating if needed):

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "npm run lint --silent 2>&1 | tail -5 || true"
      }]
    }]
  }
}

Also offer the PostToolUse lint hook while here — one setup, permanent benefit.


Called by vibe-fix-bug

When vibe-fix-bug detects an environment bug in triage (Step 1), it calls vibe-doctor automatically:

vibe-fix-bug detected: environment bug
Running doctor: to diagnose before bug workflow...
[doctor output]
If environment is fixed and app starts → close bug, no BUG_SPEC needed.
If code bug remains → continue with vibe-fix-bug significant path.

Absolute rules

Never read .env files. Skip any check that would require reading secret values. Only check that .env exists, not its contents.

Never run npm install without confirming if node_modules exists and is large. Large reinstalls take minutes. Confirm first: "node_modules exists but is stale. Run npm install? (y/n)"

Never git rm or rm -rf without explicit user confirmation. Show the command. Explain what it does. Wait for yes.

All checks are read-only except the safe remediations list. If in doubt — flag, don't fix.

If the project is not Node — skip Node checks gracefully. No errors for missing package.json on a Python project. Detect project type in Step 0 and skip inapplicable checks.

What ships with it: 1 file

4.1 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 327,069. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.