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:
- PostToolUse Hook - Automatically detects when you write ASCII diagrams to markdown files
- 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.