smithery/heyvhuang

tool-hooks-doctor

Detect whether Claude Code evolution hooks are installed/enabled, and print a copy-paste fix. Use when you expect runs/evolution artifacts but nothing is being written. Triggers: hooks, evolution, runs/evolution, settings.json, PreToolUse, PostToolUse.

Installation

$ npx skills add smithery/heyvhuang --skill tool-hooks-doctor

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/heyvhuang · top by installs.

npx skills add smithery/heyvhuang

Browse all from smithery/heyvhuang

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Declared

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Bash, Grep
Declared agents claude-code cursor opencode

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,959 B
  • docs SUMMARY.md 277 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Hooks Doctor (Claude Code)

Goal: quickly verify whether Claude Code hooks for skill-evolution are installed and enabled.

This is an atomic diagnostic tool used by other workflows so they can warn early when the evolution loop is not active.

Scope

  • Applies to: Claude Code hook installation / configuration
  • Does not apply to: Cursor/OpenCode/etc that do not support Claude-style hooks

What "healthy" looks like

  1. Hook scripts exist at:
  • ~/.claude/skills/skill-evolution/hooks/
  1. Settings enable the hooks in either:
  • Project settings: <repo_root>/.claude/settings.json
  • Or global settings: ~/.claude/settings.json
  1. A session produces artifacts under:
  • <projectroot>/runs/evolution/<runid>/...

Check (read-only)

Run these checks and report status as: OK / PARTIAL / MISSING.

1) Are the hook scripts installed?

ls -la ~/.claude/skills/skill-evolution/hooks/ 2>/dev/null || true

Required files:

  • pre-tool.sh
  • post-bash.sh
  • post-tool.sh
  • session-end.sh

If missing: user must install/update the skill-evolution skill first.

2) Are hooks enabled in settings?

Check both locations:

test -f .claude/settings.json && echo "project settings: .claude/settings.json" || true
test -f ~/.claude/settings.json && echo "global settings: ~/.claude/settings.json" || true

grep -n "skill-evolution/hooks/pre-tool.sh" .claude/settings.json ~/.claude/settings.json 2>/dev/null || true
grep -n "skill-evolution/hooks/post-bash.sh" .claude/settings.json ~/.claude/settings.json 2>/dev/null || true
grep -n "skill-evolution/hooks/post-tool.sh" .claude/settings.json ~/.claude/settings.json 2>/dev/null || true
grep -n "skill-evolution/hooks/session-end.sh" .claude/settings.json ~/.claude/settings.json 2>/dev/null || true

Interpretation:

  • If none of the grep checks match: hooks are not enabled.
  • If only some match: configuration is PARTIAL and should be fixed.

3) Quick runtime smoke test (optional)

If user confirms, run a harmless bash command in the project and then check:

ls -la runs/evolution 2>/dev/null || true

Fix (write; require confirmation)

If hooks are not enabled, recommend installing project-level hooks (safer than global).

Before applying, tell the user exactly which file will be written:

  • Project-level: <repo_root>/.claude/settings.json (recommended)
  • Global: ~/.claude/settings.json

Then wait for explicit confirmation.

Install project-level hooks (recommended)

python3 - <<'PY'
import json
from pathlib import Path

settings = Path('.claude') / 'settings.json'
settings.parent.mkdir(parents=True, exist_ok=True)

data = {}
if settings.exists():
    data = json.loads(settings.read_text() or '{}')
if not isinstance(data, dict):
    data = {}

hooks = data.get('hooks')
if not isinstance(hooks, dict):
    hooks = {}

desired = {
  'PreToolUse': [{
    'matcher': 'Bash|Write|Edit',
    'hooks': [{'type':'command','command':'bash ~/.claude/skills/skill-evolution/hooks/pre-tool.sh'}]
  }],
  'PostToolUse': [
    {
      'matcher': 'Bash',
      'hooks': [{'type':'command','command':'bash ~/.claude/skills/skill-evolution/hooks/post-bash.sh "$TOOL_OUTPUT" "$EXIT_CODE"'}]
    },
    {
      'matcher': 'Write|Edit',
      'hooks': [{'type':'command','command':'bash ~/.claude/skills/skill-evolution/hooks/post-tool.sh "$TOOL_OUTPUT" "$EXIT_CODE"'}]
    }
  ],
  'Stop': [{
    'matcher': '',
    'hooks': [{'type':'command','command':'bash ~/.claude/skills/skill-evolution/hooks/session-end.sh'}]
  }]
}

def has_command(arr, matcher, command):
    for item in arr:
        if not isinstance(item, dict):
            continue
        if item.get('matcher') != matcher:
            continue
        hs = item.get('hooks')
        if not isinstance(hs, list):
            continue
        for h in hs:
            if isinstance(h, dict) and h.get('command') == command:
                return True
    return False

for event, items in desired.items():
    arr = hooks.get(event)
    if not isinstance(arr, list):
        arr = []
    for it in items:
        cmd = it['hooks'][0]['command']
        if not has_command(arr, it['matcher'], cmd):
            arr.append(it)
    hooks[event] = arr

data['hooks'] = hooks

if settings.exists():
    backup = settings.with_suffix(settings.suffix + '.bak')
    backup.write_text(settings.read_text())

settings.write_text(json.dumps(data, indent=2, ensure_ascii=True) + '\n')
print('Installed hooks into:', settings)
PY

Install global hooks (optional)

Same as above, but write to ~/.claude/settings.json.