markdown-formatter.md 2.0 KB

Markdown Formatter Hook

Purpose: Auto-fix markdown formatting issues after file edits


Configuration

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"
      }]
    }]
  }
}

Python Script

#!/usr/bin/env python3
import json, sys, re, os

def detect_language(code):
    s = code.strip()
    if re.search(r'^\s*[{\[]', s):
        try:
            json.loads(s)
            return 'json'
        except: pass
    if re.search(r'^\s*def\s+\w+\s*\(', s, re.M):
        return 'python'
    if re.search(r'\b(function\s+\w+|const\s+\w+\s*=)', s):
        return 'javascript'
    if re.search(r'\b(SELECT|INSERT|UPDATE)\s+', s, re.I):
        return 'sql'
    return 'text'

def format_markdown(content):
    def add_lang(match):
        indent, info, body, closing = match.groups()
        if not info.strip():
            return f"{indent}```{detect_language(body)}\n{body}{closing}\n"
        return match.group(0)
    
    pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'
    content = re.sub(pattern, add_lang, content)
    return re.sub(r'\n{3,}', '\n\n', content).rstrip() + '\n'

# Main
data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')
if path.endswith(('.md', '.mdx')) and os.path.exists(path):
    with open(path, 'r') as f:
        content = f.read()
    formatted = format_markdown(content)
    if formatted != content:
        with open(path, 'w') as f:
            f.write(formatted)
        print(f"✓ Fixed formatting in {path}")

What It Fixes

  • Unlabeled code blocks → detects language
  • Excessive blank lines → max 2 consecutive
  • Trailing whitespace

Related

  • ../../lookup/hook-events.md - Hook events
  • ./formatting-hook.md - Code formatting

Reference: https://docs.anthropic.com/en/docs/claude-code/hooks