ASCII diagram alignment checker

ASCII box diagrams are great for documentation - they're portable, version-controllable, and render everywhere. LLMs are not good at generating aligned diagrams.

This post shows how to use Claude Code hooks and skills to automatically detect ASCII diagrams and verify their alignment before committing.

The problem

Claude code loves to create ascii diagrams but is not good at generating a properly aligned one. Typically the right vertical border is misaligned.

┌────────────────────┐
│  Box 1             │
│  Content          │
└────────────────────┘

The solution

Two components working together:

  1. PostToolUse Hook - Automatically detects when you write ASCII diagrams to markdown files
  2. Review ASCII Diagram Skill - Measures visual widths, identifies alignment issues and provide fix instructions

Setup

1. Create the hook

Create ~/.claude/hooks/ascii_diagram_check.py:

#!/usr/bin/env python3
"""
PostToolUse hook that detects ASCII diagrams in markdown files.
Reminds Claude to run the review-ascii-diagram skill before committing.
"""

import json
import re
import sys


# Unicode box-drawing character ranges
BOX_DRAWING_PATTERN = re.compile(r'[\u2500-\u257F\u2580-\u259F]')


def has_ascii_diagram(content: str) -> bool:
    """Check if content contains Unicode box-drawing characters."""
    return bool(BOX_DRAWING_PATTERN.search(content))


def is_markdown_file(file_path: str) -> bool:
    """Check if file is a markdown file."""
    lower_path = file_path.lower()
    return lower_path.endswith('.md') or lower_path.endswith('.mdx')


def main():
    try:
        input_data = json.load(sys.stdin)

        tool_name = input_data.get('tool_name', '')
        tool_input = input_data.get('tool_input', {})

        # Only check Write and Edit tools
        if tool_name not in ('Write', 'Edit'):
            print(json.dumps({}))
            sys.exit(0)

        file_path = tool_input.get('file_path', '')

        # Only check markdown files
        if not is_markdown_file(file_path):
            print(json.dumps({}))
            sys.exit(0)

        # Get content (Write uses 'content', Edit uses 'new_string')
        content = tool_input.get('content') or tool_input.get('new_string', '')

        # Check for ASCII diagrams
        if has_ascii_diagram(content):
            result = {
                "decision": "block",
                "reason": (
                    f"ASCII diagram detected in {file_path}. "
                    "Run the review-ascii-diagram skill to check alignment "
                    "before proceeding."
                )
            }
            print(json.dumps(result))
        else:
            print(json.dumps({}))

    except Exception:
        print(json.dumps({}))

    sys.exit(0)


if __name__ == '__main__':
    main()

2. Register the hook in settings

Add to ~/.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/ascii_diagram_check.py"
          }
        ]
      }
    ]
  }
}

3. Create the skill

Create ~/.claude/skills/review-ascii-diagram/skill.md:

---
name: Review ASCII Diagram
description: Review ASCII box diagrams for alignment issues.
version: 1.0.0
---

# Review ASCII Diagram

This skill reviews ASCII box diagrams for alignment issues.

## Measuring Visual Width

Unicode box-drawing characters take multiple bytes but display as 1 width.
Use Python to measure:

python3 -c "
import unicodedata

def visual_width(s):
width = 0
for c in s:
if unicodedata.east_asian_width(c) in ('F', 'W'):
width += 2 # Full-width chars
else:
width += 1 # All others including box-drawing
return width

with open('FILE_PATH', 'r') as f:
lines = f.readlines()
for i in range(START_LINE - 1, END_LINE):
line = lines[i].rstrip('\n')
print(f'{i + 1:4d} | width={visual_width(line):3d} | {line}')
"

## Review Process

1. Identify the diagram boundaries
2. Measure all lines with the visual width script
3. Check for inconsistent widths
4. Provide specific fix instructions

## Output Format

| Line | Width | Content                    |
| ---- | ----- | -------------------------- |
| 10   | 22    | ┌────────────────────┐     |
| 11   | 22    | │ Header │                 |
| 12   | 21    | │ Problem line │ <-- ISSUE |
| 13   | 22    | └────────────────────┘     |

Fix: Line 12 has width 21, should be 22. Add 1 space before the right border.