Create ASCII architecture diagrams for ADRs using graph-easy. Use when writing ADRs, adding diagrams to existing ADRs, or when user mentions "ADR diagram", "architecture diagram", "ASCII diagram". Zero external dependencies for rendering - pure text output embeds directly in markdown.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
references/diagram-examples.mdCreate comprehensive ASCII architecture diagrams for Architecture Decision Records (ADRs) using graph-easy. Pure text output with automatic layout - no image rendering required.
Run these checks in order. Each layer depends on the previous.
# Detect OS and set package manager
case "$(uname -s)" in
Darwin) PM="brew" ;;
Linux) PM="apt" ;;
*) echo "ERROR: Unsupported OS (require macOS or Linux)"; exit 1 ;;
esac
command -v $PM &>/dev/null || { echo "ERROR: $PM not installed"; exit 1; }
echo "✓ Package manager: $PM"
# Prefer mise for unified tool management
if command -v mise &>/dev/null; then
# Install Perl via mise
mise which perl &>/dev/null || mise install perl
# Install cpanminus under mise perl
mise exec perl -- cpanm --version &>/dev/null 2>&1 || {
echo "Installing cpanminus under mise perl..."
mise exec perl -- curl -L https://cpanmin.us | mise exec perl -- perl - App::cpanminus
}
echo "✓ cpanminus installed (via mise perl)"
else
# Fallback: Install cpanminus via system package manager
command -v cpanm &>/dev/null || {
echo "Installing cpanminus via $PM..."
case "$PM" in
brew) brew install cpanminus ;;
apt) sudo apt install -y cpanminus ;;
esac
}
echo "✓ cpanminus installed"
fi
# Check if Graph::Easy is installed (mise-first)
if command -v mise &>/dev/null; then
mise exec perl -- perl -MGraph::Easy -e1 2>/dev/null || {
echo "Installing Graph::Easy via mise perl cpanm..."
mise exec perl -- cpanm Graph::Easy
}
echo "✓ Graph::Easy installed (via mise perl)"
else
perl -MGraph::Easy -e1 2>/dev/null || {
echo "Installing Graph::Easy via cpanm..."
cpanm Graph::Easy
}
echo "✓ Graph::Easy installed"
fi
# Verify graph-easy is accessible and functional
command -v graph-easy &>/dev/null || {
echo "ERROR: graph-easy not found in PATH"
exit 1
}
# Test actual functionality (--version exits with code 2, unreliable)
echo "[Test] -> [OK]" | graph-easy &>/dev/null && echo "✓ graph-easy ready"
# Copy-paste this entire block to ensure graph-easy is ready (macOS + Linux)
# Prefers mise for unified cross-platform tool management
# Check for mise first (recommended)
if command -v mise &>/dev/null; then
echo "Using mise for Perl management..."
mise which perl &>/dev/null || mise install perl
mise exec perl -- cpanm --version &>/dev/null 2>&1 || \
mise exec perl -- curl -L https://cpanmin.us | mise exec perl -- perl - App::cpanminus
mise exec perl -- perl -MGraph::Easy -e1 2>/dev/null || mise exec perl -- cpanm Graph::Easy
else
# Fallback: system package manager
echo "💡 Tip: Install mise for unified tool management: curl https://mise.run | sh"
case "$(uname -s)" in
Darwin) PM="brew" ;;
Linux) PM="apt" ;;
*) echo "ERROR: Unsupported OS"; exit 1 ;;
esac
command -v $PM &>/dev/null || { echo "ERROR: $PM not installed"; exit 1; }
command -v cpanm &>/dev/null || { [ "$PM" = "apt" ] && sudo apt install -y cpanminus || brew install cpanminus; }
perl -MGraph::Easy -e1 2>/dev/null || cpanm Graph::Easy
fi
# Verify graph-easy is in PATH and functional
command -v graph-easy &>/dev/null || {
echo "ERROR: graph-easy not in PATH after installation"
exit 1
}
# Test actual functionality (--version exits with code 2, unreliable)
echo "[Test] -> [OK]" | graph-easy &>/dev/null && echo "✓ graph-easy ready"
# Nodes (square brackets)
[Node Name]
# Edges (arrows)
[A] -> [B]
# Labeled edges
[A] -- label --> [B]
# Bidirectional
[A] <-> [B]
# Chain
[A] -> [B] -> [C]
# Named group with dashed border
( Group Name:
[Node A]
[Node B]
)
# Nested connections
( Before:
[Old System]
)
( After:
[New System]
)
[Before] -> [After]
# Custom label (different from ID)
[db] { label: "PostgreSQL Database"; }
# ASCII markers for visual distinction INSIDE boxes
# (emojis break box alignment - use ASCII markers instead)
[deleted] { label: "[x] Old Component"; }
[added] { label: "[+] New Component"; }
[warning] { label: "[!] Deprecated"; }
[success] { label: "[OK] Passed"; }
Character rules for nodes:
Use graph { label: "..."; } for graphical emojis in title/legend.
Example: Emoji breaks alignment (DON'T DO THIS)
# BAD - emoji inside node
[rocket] { label: "🚀 Launch"; }
Renders broken:
┌────────────┐
│ 🚀 Launch │ <-- box edge misaligned due to double-width emoji
└────────────┘
Example: ASCII marker preserves alignment (DO THIS)
# GOOD - ASCII marker inside node
[rocket] { label: "[>] Launch"; }
Renders correctly:
┌────────────┐
│ [>] Launch │
└────────────┘
Example: Emoji safe in graph title (OK)
# OK - emoji in graph label (outside boxes)
graph { label: "🚀 Deployment Pipeline"; flow: east; }
[Build] -> [Test] -> [Deploy]
Renders correctly (emoji in title, not in boxes):
🚀 Deployment Pipeline
┌───────┐ ┌──────┐ ┌────────┐
│ Build │ --> │ Test │ --> │ Deploy │
└───────┘ └──────┘ └────────┘
# MANDATORY: Always specify flow direction explicitly
graph { flow: south; } # Top-to-bottom (architecture, decisions)
graph { flow: east; } # Left-to-right (pipelines, sequences)
Never rely on default flow - explicit is clearer.
Emojis break alignment INSIDE boxes but are SAFE in graph titles/legends.
Emoji Selection Guide - Choose emoji that matches diagram purpose:
| Diagram Type | Emoji | Example Title |
|---|---|---|
| Migration/Change | 🔄 | "🔄 Database Migration" |
| Deployment/Release | 🚀 | "🚀 Deployment Pipeline" |
| Data Flow | 📊 | "📊 Data Ingestion Flow" |
| Security/Auth | 🔐 | "🔐 Authentication Flow" |
| Error/Failure | ⚠️ | "⚠️ Error Handling" |
| Decision/Branch | 🔀 | "🔀 Routing Decision" |
| Architecture | 🏗️ | "🏗️ System Architecture" |
| Network/API | 🌐 | "🌐 API Integration" |
| Storage/Database | 💾 | "💾 Storage Layer" |
| Monitoring/Observability | 📡 | "📡 Monitoring Stack" |
| Hook/Event | 🪝 | "🪝 Hook Flow" |
| Before/After comparison | ⏮️/⏭️ | "⏮️ Before" / "⏭️ After" |
# Title with semantic emoji
graph { label: "🚀 Deployment Pipeline"; flow: east; }
# Title with legend (multiline using \n)
graph { label: "🪝 Hook Flow\n──────────\n✓ Allow ✗ Deny ⚠ Warn"; flow: south; }
Rendered:
Hook Flow
──────────
✓ Allow ✗ Deny ⚠ Warn
╭───────╮
│ Start │
╰───────╯
Rule: Emojis ONLY in graph { label: "..."; } - NEVER inside [ node ]
# Rounded corners for start/end nodes
[ Start ] { shape: rounded; }
[ End ] { shape: rounded; }
# Double border for emphasis
[ Critical Step ] { border: double; }
# Bold border for important nodes
[ Key Decision ] { border: bold; }
# Dotted border for optional/skippable
[ Optional ] { border: dotted; }
# Multiline labels with \n
[ Hook Input\n(stdin JSON) ]
Rendered examples:
╭─────────╮ ┌─────────┐
│ Rounded │ │ Default │
╰─────────╯ └─────────┘
╔═════════╗ ┏━━━━━━━━━┓
║ Double ║ ┃ Bold ┃
╚═════════╝ ┗━━━━━━━━━┛
Note: Dotted borders (
{ border: dotted; }) use⋮characters that render inconsistently on GitHub. Use sparingly.
[ A ] -> [ B ] # Solid arrow (default)
[ A ] ..> [ B ] # Dotted arrow
[ A ] ==> [ B ] # Bold/double arrow
[ A ] - -> [ B ] # Dashed arrow
[ A ] -- label --> [ B ] # Labeled edge
graph { flow: south; }
[Before] -- migrate --> [After]
graph { flow: south; }
[A] -> [B] -> [C]
[B] -> [D]
graph { flow: east; }
[Input] -> [Process] -> [Output]
graph { flow: south; }
[Decision] -> [Option A]
[Decision] -> [Option B]
( Group:
[Component 1]
[Component 2]
)
[External] -> [Component 1]
[Client] <-> [Server]
[Server] -> [Database]
# MANDATORY: Always use --as=boxart for clean output
graph-easy --as=boxart << 'EOF'
graph { flow: south; }
[A] -> [B] -> [C]
EOF
Never use --as=ascii - it produces ugly +--+ boxes instead of clean ┌──┐ lines.
| Mode | Command | Usage |
|---|---|---|
boxart | --as=boxart | MANDATORY - clean Unicode lines |
ascii | --as=ascii | NEVER USE - ugly output, legacy only |
# 1. Write DSL to heredoc
# 2. Render with boxart
graph-easy --as=boxart << 'EOF'
[Your] -> [Diagram] -> [Here]
EOF
# 3. Review output
# 4. Iterate if needed
# 5. Copy final ASCII to ADR
CRITICAL: Every rendered diagram MUST be followed by a collapsible <details> block containing the graph-easy source code. This is non-negotiable for:
## Architecture
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Before │ ──> │ After │ ──> │ Database │
└──────────┘ └──────────┘ └──────────┘
```
<details>
<summary>graph-easy source</summary>
```
graph { flow: east; }
[Before] -> [After] -> [Database]
```
</details>
The <details> block is MANDATORY - never embed a diagram without its source.
GitHub Flavored Markdown supports HTML <details> and <summary> tags for collapsible sections. Key syntax rules:
Structure:
<details>
<summary>Click to expand</summary>
<!-- BLANK LINE REQUIRED HERE -->
Content goes here (Markdown supported)
<!-- BLANK LINE REQUIRED HERE -->
</details>
Critical rules:
<summary> and before </details> for Markdown to render<details> and <summary> must be at column 0 (no leading spaces)<summary> appears as the collapsed headerOptional: Default expanded:
<details open>
<summary>Expanded by default</summary>
Content visible on page load
</details>
Common mistake (Markdown won't render):
<details>
<summary>Broken</summary>
No blank line - this won't render as Markdown!
</details>
References:
No separate asset files needed - diagram is inline in the markdown.
If ADR changes, regenerate by running the source through graph-easy again:
# Extract source from <details> block, pipe through graph-easy
graph-easy --as=boxart << 'EOF'
# paste source here
EOF
Avoid emojis - they have variable width and break box alignment on GitHub.
| Meaning | Marker |
|---|---|
| Added/New | [+] |
| Removed/Deleted | [x] |
| Changed/Updated | [*] |
| Warning/Deprecated | [!] |
| Deferred/Pending | [~] |
| Current/Active | [>] |
| Optional | [?] |
| Locked/Fixed | [=] |
─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ (light)
═ ║ ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬ (double)
→ ← ↑ ↓ (arrows)
∨ ∧ (logic - graph-easy uses these)
< > ^ v (ASCII arrows)
• ○ ● (bullets)
□ ■ (squares)
◇ ◆ (diamonds)
× ÷ ± ≠ ≤ ≥ ∞ (math)
∧ ∨ ¬ (logic)
# Vertical flow (architecture)
graph { flow: south; }
# Horizontal flow (pipeline)
graph { flow: east; }
# Labeled edge
[A] -- label text --> [B]
# Group with border
( Group Name:
[Node A]
[Node B]
)
# Custom node label
[id] { label: "Display Name"; }
WARNING: This is the most commonly forgotten requirement. Diagrams without labels are invalid.
graph { label: "🔄 Database Migration"; flow: south; }
[Old DB] -> [New DB]
graph { flow: south; }
[Old DB] -> [New DB]
Why this is wrong: Missing label: with emoji. The preflight validator will BLOCK any ADR containing diagrams without graph { label: "emoji ..."; }.
graph { label: "🚀 Title"; } - semantic emoji + title (MOST FORGOTTEN - check first!)graph { flow: south; } or graph { flow: east; } - explicit direction--as=boxart - NEVER --as=ascii<details> block with source - EVERY diagram MUST have collapsible source code block``` block, followed immediately by <details><summary>graph-easy source</summary> with source in ``` block{ shape: rounded; } - entry/exit points{ border: double; } or { border: bold; }{ border: dotted; }┌──┐ border)\n for multiline - max ~15 chars per line-> solid arrow..> dotted arrow==> bold arrow-- YES -->, -- error -->graph { label: "..."; } title( Name: ... ) used for logical clustering when 4+ related nodes[db] { label: "PostgreSQL"; }<details> block with graph-easy DSL source immediately after the rendered output┌──┐, not ASCII +--+\n, no truncation\n for complex diagrams( Group: ... ) containers| Issue | Cause | Solution |
|---|---|---|
command not found | graph-easy not installed | Run preflight check |
| Misaligned boxes | Used --as=ascii | Always use --as=boxart |
| Box border broken | Graphical emoji in node | Remove 🚀💡, use ✓✗ or [x][+] |
| Nodes overlap | Too complex | Split into multiple diagrams (max 7-10 nodes) |
| Edge labels cut off | Label too long | Shorten to 1-3 words |
| No title showing | Wrong syntax | Use graph { label: "Title"; flow: south; } |
| Weird layout | No flow direction | Add graph { flow: south; } or flow: east |
| Parse error | Special chars in node | Escape or simplify node names |