Installs into .claude/skills of the current project.
Are you the author of Gen Diagram?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sgaunet-gen-diagram)
---
name: gen-diagram
description: Generate a d2 architecture diagram (https://d2lang.com/) using icons from icons.terrastruct.com. Renders SVG when the d2 binary is installed.
argument-hint: "[--type=arch|sequence|er|deployment|c4|pipeline] [--no-render] [--no-mise] [--output=<path>] [--dry-run] [<description>]"
user-invocable: true
disable-model-invocation: true
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(d2:*), Bash(mise:*), Bash(git:*), Bash(test:*), Bash(grep:*), Bash(mkdir:*), Bash(command:*), Task, AskUserQuestion
---
# Generate d2 Diagram
Produce a `.d2` source file (and a rendered `.svg` when the `d2` binary is installed) that captures a software or infrastructure architecture. Default engine is **d2** (https://d2lang.com/), default icons come from **https://icons.terrastruct.com/**.
## Why This Skill Exists
**Problem**: Architecture diagrams in design docs and READMEs are usually either missing, drawn in a closed-source tool that no one can edit, or pasted as PNG screenshots that drift from reality. Running through d2 syntax and the icon catalog by hand takes 10-20 minutes per diagram.
**Solution**: One invocation that:
- Synthesizes a `.d2` source file from a free-text description or repo analysis
- Embeds vendor icons from `icons.terrastruct.com` only when the URL is known to resolve
- Renders to SVG automatically if `d2` is installed
- Stores both files in `docs/diagrams/` for diff-able, PR-reviewable diagrams
## Content Generation Rules
**CRITICAL**: When generating any content (.d2 source, file headers, descriptions, or any other output):
- **NEVER** add Claude Code attribution or co-authorship
- **NEVER** include phrases like "Generated by Claude", "Created with Claude Code", or similar
- **NEVER** add "Co-Authored-By: Claude" in commit messages or documentation
- All generated content should appear as if written by a human developer
- Focus on technical accuracy and clear, readable d2 source
## Process (6 Phases)
### Phase 1: Prerequisites Validation
1. **Validate Git repository:**
```bash
git rev-parse --git-dir
```
- If fails: Exit with error "Not a git repository. Run 'git init' first."
2. **Detect d2 and the mise toolchain** (non-blocking, and **no writes in this phase**):
```bash
command -v d2
command -v mise
test -f mise.toml && grep -qE '^[[:space:]]*"?d2"?[[:space:]]*=' mise.toml
```
- `d2` on PATH: store `d2Installed = true`. Capture version: `d2 --version`.
- `d2` absent: store `d2Installed = false` (will warn later, but proceed — the `.d2`
source is produced either way).
- `mise` on PATH: store `miseAvailable = true`.
- `mise.toml` already pins d2: store `d2Pinned = true`.
When `miseAvailable && !d2Pinned && !--no-mise`, the pin is added in **Phase 5** and
disclosed in the Phase 4 preview first. `mise use` edits `mise.toml`, which is a project
file change and must pass through the confirmation gate like any other write.
3. **Verify skill assets:**
- Use Read to confirm `${CLAUDE_PLUGIN_ROOT}/assets/gen-diagram/d2-cheatsheet.md` exists.
- If missing: Exit with error "Cheatsheet not found at ${CLAUDE_PLUGIN_ROOT}/assets/gen-diagram/d2-cheatsheet.md. Plugin may be corrupted. Reinstall the software-engineering plugin."
4. **Resolve repository root** for output path defaults:
```bash
git rev-parse --show-toplevel
```
Store as `repoRoot`.
### Phase 2: Argument Parsing
Parse `$ARGUMENTS` (or `$argument`, depending on host syntax). Tokenize on spaces, then partition into flags and a free-text remainder.
**Supported flags:**
- `--type=<arch|sequence|er|deployment|c4|pipeline>` — diagram style. Default: inferred from description, falls back to `arch`.
- `--no-render` — skip the SVG render step even if d2 is installed.
- `--output=<path>` — explicit target path for the `.d2` file. Default: `docs/diagrams/<slug>.d2`.
- `--dry-run` — produce the preview only; do not write any files.
- `--no-mise` — never touch `mise.toml`; rely on whatever `d2` is already on PATH.
**Everything else** is joined back with spaces as `description`.
**Slugification:**
- If `description` is non-empty: lowercase, replace non-alphanumerics with `-`, collapse repeats, trim leading/trailing `-`, cap at 60 chars.
- If `description` is empty: slug = `architecture`.
**Type inference** (when `--type` is omitted):
- Description matches `/sequence|flow|step.*step|request.*response/i` → `sequence`
- Matches `/er|entity|schema|table/i` → `er`
- Matches `/deploy|infra|vpc|cluster|kubernetes|aws|gcp|azure/i` → `deployment`
- Matches `/c4|context|container|component/i` → `c4`
- Matches `/pipeline|ci\/cd|workflow|jobs/i` → `pipeline`
- Otherwise → `arch`
**Default output path:**
- If `--output` provided: use it verbatim (resolve relative to cwd).
- Else: `<repoRoot>/docs/diagrams/<slug>.d2`.
The accompanying SVG path is the same path with `.d2` replaced by `.svg`.
### Phase 3: Context Gathering
1. **Read the cheatsheet** to ground the LLM in real d2 syntax and verified icon URLs:
```
Read: ${CLAUDE_PLUGIN_ROOT}/assets/gen-diagram/d2-cheatsheet.md
```
2. **If `description` is empty**, launch one Explore subagent to summarize the repo's architecture:
```
Task(
subagent_type: "Explore",
description: "Repo architecture summary",
prompt: "
Summarize this repository's architecture for a system diagram. Return:
{
entryPoints: [list of binaries / servers / endpoints discovered],
services: [list of internal services or modules],
datastores: [databases, caches, queues with vendor names],
externalDeps: [third-party APIs, SaaS],
deploymentTargets: [k8s, AWS service, etc., if obvious from IaC],
language: dominant language,
shape: 'monolith' | 'modular' | 'microservices' | 'library' | 'cli'
}
Detection strategy:
- Languages: glob for *.go, *.py, *.js, *.ts, *.rs, *.java
- Entry points: cmd/*/main.go, main.py, src/index.{ts,js}, bin/*
- Datastores: search go.mod / package.json / requirements.txt for postgres, mysql, redis, kafka, mongo, sqlite, elasticsearch
- Deployment: presence of Dockerfile, k8s/, helm/, *.tf, .github/workflows/
Return concise JSON, no prose.
"
)
```
Use the returned summary as the modeling input. Otherwise, skip this step.
3. **If `description` is non-empty**, treat it as the modeling input directly. Use the cheatsheet patterns to expand abbreviations (e.g. "auth flow with JWT and Redis" → sequence diagram with user, SPA, API, Redis cache).
### Phase 4: Confirmation Preview
Build the preview message:
```
📐 d2 Diagram Preview
Type: <type>
Description: <description or "(auto-detected from repo)">
Output: <relative path>.d2
Render: <"yes (d2 → SVG)" if d2Installed && !--no-render else "no (source only)">
d2 tooling: <"mise.toml → pin d2 0.7.1 (new)" | "mise.toml already pins d2" | "PATH d2 <version>" | "none — source only">
Planned content:
• Layout: <direction> / <layout-engine>
• Nodes (~N): <comma-separated short list>
• Icons: <count of icons.terrastruct.com URLs that will be embedded>
<CONDITIONAL WARNINGS>
```
**Conditional warnings:**
If `d2Installed = false`:
```
ℹ️ d2 binary not found
.d2 source will be created; rendering depends on provisioning d2.
Preferred — pins the version for this repo, local dev and CI alike:
• mise use d2@0.7.1 # adds d2 to mise.toml [tools] and installs it
Without mise:
• macOS: brew install d2
• Linux: curl -fsSL https://d2lang.com/install.sh | sh -s --
• Verify: d2 --version
```
If `miseAvailable && !d2Pinned && !--no-mise`:
```
ℹ️ mise.toml will gain a d2 pin
Adding: d2 = "0.7.1" (under [tools])
<"mise.toml will be created" | "existing pins are preserved">
This keeps local renders and CI on the same d2 version.
Skip with --no-mise.
```
If target `.d2` file already exists:
```
⚠️ WARNING: <path>.d2 already exists
This will OVERWRITE the existing diagram source.
Consider committing or backing up the current version first.
```
**Confirmation logic:**
If `--dry-run`:
```
[DRY RUN MODE - No files will be written]
```
Proceed straight to Phase 6 to print the preview and the would-be `.d2` content. Do **not** write or render.
If target `.d2` exists and not `--dry-run`:
```
Use AskUserQuestion:
Question: "Overwrite existing diagram at <path>.d2?"
Options:
- "Yes, overwrite" (proceed to Phase 5)
- "No, cancel" (exit with "Operation cancelled by user")
```
If target does not exist and not `--dry-run`:
```
Use AskUserQuestion:
Question: "Create diagram at <path>.d2?"
Options:
- "Yes, create" (proceed to Phase 5)
- "No, cancel" (exit with "Operation cancelled by user")
```
### Phase 5: Generation
1. **Synthesize the `.d2` source** following the cheatsheet:
- Begin with a 1-3 line comment header: `# <Title>` and a sentence on scope/audience.
- For `sequence`: start the body with `shape: sequence_diagram`.
- For `er`: each table is a `shape: sql_table` block.
- For `deployment`: nest containers by trust boundary (region → VPC → subnet).
- For `c4`: use `shape: c4-person` for actors and `shape: package` for containers.
- For `arch` / `pipeline`: pick `direction: right` and label every edge.
- **Only include `icon:` URLs that appear in the cheatsheet's verified list.** If the design needs an icon not in the list, omit it (the user can add it manually) rather than guessing.
- Apply layout engine `dagre` by default. Switch to `elk` if node count > 10.
- Keep node count ≤ 12 per top-level view.
2. **Ensure parent directory exists:**
```bash
mkdir -p "$(dirname <output>)"
```
3. **Write the file** with Write to the resolved output path.
4. **Pin d2 in `mise.toml`** when `miseAvailable && !d2Pinned && !--no-mise`:
```bash
mise use d2@0.7.1
```
Run it from `repoRoot`. `mise use` is idempotent: it records `d2 = "0.7.1"` under
`[tools]`, creates `mise.toml` when absent, leaves every other pin untouched, and
installs the binary — so the Phase 6 render can proceed even if d2 was missing.
On success set `d2Installed = true` and `misePinWritten = true`.
- On failure (no network, unknown version, untrusted config) report the stderr as a
**soft failure**, leave `d2Installed` unchanged, and continue — the `.d2` source is
written regardless. A first run may need `mise trust` before `mise use` succeeds.
### Phase 6: Render & Success Report
1. **Render to SVG** when `d2Installed && !--no-render && !--dry-run`:
```bash
d2 <output>.d2 <output>.svg
```
If d2 was just provisioned by the Phase 5 `mise use` it may not be on PATH in this
shell yet — route through mise instead:
```bash
mise exec -- d2 <output>.d2 <output>.svg
```
Capture exit code. If non-zero, report the d2 error to the user but treat as a soft failure (the `.d2` source was still produced).
2. **Verify written files:**
```bash
test -f <output>.d2 && wc -l <output>.d2
test -f <output>.svg # only if render attempted
```
3. **Success report (normal execution):**
```
✅ Diagram created
Files:
✓ <output>.d2 (<N> lines)
✓ <output>.svg # only if render succeeded
✓ mise.toml (d2 = "0.7.1") # only if misePinWritten
Type: <type>
Layout: <direction> / <layout-engine>
Icons used: <count>
Next steps:
1. Preview the SVG:
open <output>.svg # macOS
xdg-open <output>.svg # Linux
2. Edit the source:
${EDITOR:-vi} <output>.d2
3. Re-render after edits:
d2 <output>.d2 <output>.svg
4. Try a different layout:
d2 --layout=elk <output>.d2 <output>.svg
5. Embed in docs:

6. Browse the icon catalog:
https://icons.terrastruct.com/
7. d2 reference:
https://d2lang.com/tour/intro
```
4. **Success report (dry-run):**
```
✅ [DRY RUN] Preview only — no files written
Would create:
• <output>.d2 (~<N> lines)
• <output>.svg # only if d2Installed && !--no-render
--- Preview of <output>.d2 ---
<full d2 source>
--- end preview ---
Run again without --dry-run to write.
```
5. **If `d2Installed = false`**, append:
```
⚠️ Install d2 to render automatically next time:
mise use d2@0.7.1 # preferred: pins it in mise.toml for dev + CI
# or: brew install d2
# or: curl -fsSL https://d2lang.com/install.sh | sh -s --
```
6. **If `d2` render failed**, append:
```
⚠️ d2 render failed (.d2 source was written successfully):
<stderr from d2>
Try manually: d2 <output>.d2 <output>.svg
```
## Tool Provisioning (mise)
d2 is pinned through [mise](https://mise.jdx.dev) rather than installed ad hoc, so the
version that renders a diagram locally is the version CI renders it with.
**Pinned version: `d2@0.7.1`.** Bump it in one place — this section — and in the Phase 4/5/6
strings above.
```bash
mise use d2@0.7.1
```
What that does:
- Records `d2 = "0.7.1"` under `[tools]` in the repo-root `mise.toml`
- Creates `mise.toml` if the repo has none; preserves every existing pin if it does
- Installs the binary, so a render can follow immediately in the same run
- Is idempotent — re-running it is a no-op once the pin matches
Rules this skill follows:
- **Detect in Phase 1, write in Phase 5.** `mise use` mutates a tracked project file, so it
sits behind the same confirmation gate as the `.d2` output and never runs under `--dry-run`.
- **Never rewrite an existing pin.** If `mise.toml` already lists d2 at any version, leave it
alone and use it — the project's choice wins over this skill's default.
- **Opt out with `--no-mise`** for repos that manage tooling another way (Nix, Homebrew,
devcontainer, distro packages).
- **Soft-fail only.** A failed `mise use` never blocks the `.d2` source from being written.
Once pinned, collaborators and CI get d2 from:
```bash
mise install # provision every pinned tool
mise exec -- d2 --version
```
CI needs no extra step when the pipeline already uses `jdx/mise-action` (GitHub/Forgejo) or
the `jdx/mise` image (GitLab) — the same pattern the go-specialist CI generators emit.
## Integration with Other Skills
- **Before `/gen-diagram`**: none required.
- **After `/gen-diagram`**:
- `/gen-claude` will pick up the new `docs/diagrams/` directory in subsequent runs.
- Reference the SVG from generated docs or PR descriptions.
- **Companion agent**: invoking `/gen-diagram` proactively pulls in the `diagram-architect` agent for ongoing edits to `.d2` files in the repo.
## Examples
```bash
# Architecture from a description, render SVG if d2 is installed
/gen-diagram simple web app with frontend, API, postgres, redis
# Sequence diagram for an auth flow, custom output path
/gen-diagram --type=sequence --output=docs/diagrams/login.d2 login flow with JWT
# ER diagram from existing schema description
/gen-diagram --type=er users have many orders, orders have many line items
# Auto-discover from the repo (no description)
/gen-diagram
# Preview only, don't write
/gen-diagram --dry-run kafka-fronted ingestion service
# Skip rendering even when d2 is installed
/gen-diagram --no-render deployment topology on AWS
```
## Limitations & Caveats
- **d2 binary required for SVG**: only the source file is produced when `d2` is missing and cannot be provisioned. `mise use d2@0.7.1` fixes that permanently for the repo; `--no-mise` opts out.
- **Icon catalog drift**: only icons listed in the cheatsheet are guaranteed to resolve. Add more by browsing https://icons.terrastruct.com/ and updating the cheatsheet.
- **Repo analysis is best-effort**: when `description` is empty, the Explore subagent summarizes what it can find — clean up the resulting diagram by hand for unusual layouts.
- **No PNG/PDF**: only SVG is rendered by default. Use `d2 --target=png` manually for raster output.
- **Node count cap**: diagrams with more than ~12 top-level nodes get crowded. Split into multiple files (`-context.d2`, `-containers.d2`) rather than fighting the layout.
## Error Recovery
**If `mise use d2@0.7.1` fails:**
1. Untrusted config is the usual cause: `mise trust` in the repo root, then retry.
2. Unknown version — check what the registry offers: `mise ls-remote d2`.
3. No network or a locked-down environment: fall back to `brew install d2`, or re-run with
`--no-mise` to skip the pin entirely. The `.d2` source is written either way.
**If `d2 --version` errors or `d2` rendering fails:**
1. Check version: `d2 --version` (require ≥ 0.6), or `mise exec -- d2 --version` when pinned.
2. Try a different layout: `d2 --layout=elk <file>.d2`.
3. Validate syntax by reading the d2 stderr — it usually points at the offending line.
**If an icon URL 404s:**
1. Open https://icons.terrastruct.com/ and confirm the exact path (spaces become `%20`).
2. Either fix the URL in the `.d2` file or remove the `icon:` attribute and the surrounding `shape: image`.
**If the diagram is too dense:**
1. Re-run with `--type=c4` to layer it.
2. Split into separate files per concern (auth, billing, ingestion, …).
3. Increase grouping depth so siblings share a parent container.
## Additional Resources
- d2 documentation: https://d2lang.com/
- mise (tool version manager): https://mise.jdx.dev/
- mise registry (d2 backend): https://mise.jdx.dev/registry.html
- d2 tour: https://d2lang.com/tour/intro
- d2 themes: https://d2lang.com/tour/themes
- Icon catalog: https://icons.terrastruct.com/
- d2 playground: https://play.d2lang.com/
- Companion agent: `diagram-architect` (auto-activates on `.d2` files)