Generate Excalidraw architecture diagrams for the current repository. Use when the user asks to "diagram the architecture", "map project structure", "visualize module boundaries", "show data flow", or "draw system design". Creates 3 diagrams: folder structure, data flow, and system architecture. Saves output to docs/diagrams/ in the project.
Generate Excalidraw diagrams for the current repository, export them to SVG,
and update README.md — all in one run.
/arch-diagram all — Generate all 3 diagrams (default)/arch-diagram structure — Folder/file structure only/arch-diagram dataflow — Data flow diagram only/arch-diagram architecture — System architecture onlyIf no argument given, generate all 3.
Before drawing anything, gather context:
# Get project structure (2 levels deep, ignore noise)
find . -maxdepth 3 -type f \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/dist/*' \
-not -path '*/build/*' \
-not -path '*/.next/*' \
-not -path '*/gen/*' \
-not -name '*.lock' \
-not -name '*.sum' \
| head -80
# Get directory tree (folders only)
find . -maxdepth 3 -type d \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/dist/*' \
-not -path '*/build/*' \
| sort
# Check for key config files that reveal the stack
cat package.json 2>/dev/null | head -30
cat go.mod 2>/dev/null | head -20
cat buf.yaml 2>/dev/null || cat buf.gen.yaml 2>/dev/null
ls proto/ 2>/dev/null || ls *.proto 2>/dev/null
Read the project's CLAUDE.md if it exists for additional context about the architecture, conventions, and structure.
mkdir -p docs/diagrams
Use the Excalidraw MCP tool (create_view) to render each diagram
inline in the conversation. Follow the Excalidraw format rules strictly.
cameraUpdate as the first element#a5d8ff — Frontend / UI layer#b2f2bb — Backend / API layer#ffd8a8 — External services / infra#d0bfff — Middleware / processing#c3fae8 — Data / storage layer#fff3bf — Config / definitions (like .proto files)#ffc9c9 — Generated code (don't edit)roundness: { "type": 3 } on all rectanglesNEVER use the label property on rectangles — it is not supported and silently drops all text.
Instead, pair every rectangle with one or more separate text elements positioned inside it:
{"type":"rectangle","id":"box1","x":100,"y":100,"width":200,"height":80,
"strokeColor":"#339af0","backgroundColor":"#a5d8ff","fillStyle":"solid","opacity":100,"roundness":{"type":3}},
{"type":"text","id":"box1_title","x":100,"y":112,"width":200,"height":20,
"text":"ComponentName","fontSize":16,"fontFamily":2,"textAlign":"center",
"strokeColor":"#1971c2","backgroundColor":"transparent","fillStyle":"solid","opacity":100},
{"type":"text","id":"box1_body","x":110,"y":138,"width":180,"height":36,
"text":"file.tsx\nhelper.ts","fontSize":13,"fontFamily":2,"textAlign":"center",
"strokeColor":"#333","backgroundColor":"transparent","fillStyle":"solid","opacity":100}
Rules for text positioning:
y = rect.y + 10, x = rect.x, width = rect.width, textAlign: "center"y = title.y + title.height + 6, same x/width, textAlign: "center"height on text elements to roughly fontSize * lineCount * 1.4\n for multi-line text within a single text elementPurpose: Show the directory layout with color-coded layers.
Layout approach:
Draw each directory as a labeled rectangle. Use arrows to show parent → child relationships. Group with translucent zone backgrounds.
Purpose: Show how data moves through the system end-to-end.
Layout approach:
Purpose: High-level birds-eye view of the entire system.
Run this before every architecture diagram regeneration — skip drawing if nothing changed.
src/components/*.tsx (direct children only, skip subdirectories like figma/)docs/diagrams/architecture.excalidraw exists, read it and join all text values from type:"text" elements into one big string.tsx), check if it appears anywhere in that combined stringArchitecture diagram is up to date — skipping and stop here for Diagram C (no create_view, no file write)New components detected: [name, ...] and proceed with regeneration belowAfter rendering each diagram inline with the Excalidraw tool, save the element JSON for all diagrams that were generated or updated.
Write to docs/diagrams/:
structure.excalidraw (if structure was generated)dataflow.excalidraw (if dataflow was generated)architecture.excalidraw (if architecture was generated)For each diagram that was generated or updated in this run, export it to SVG.
Write docs/diagrams/_gen-svg.mjs with the following content, setting DIAGRAMS to only
include the diagrams that were actually regenerated:
import { readFileSync, writeFileSync, existsSync } from "fs";
const DIAGRAMS = [
{
input: "docs/diagrams/structure.excalidraw",
output: "docs/diagrams/structure.svg",
},
{
input: "docs/diagrams/dataflow.excalidraw",
output: "docs/diagrams/dataflow.svg",
},
{
input: "docs/diagrams/architecture.excalidraw",
output: "docs/diagrams/architecture.svg",
},
];
function esc(s) {
return String(s)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
function toSvg(path) {
const data = JSON.parse(readFileSync(path, "utf-8"));
const els = data.elements.filter((e) => e.type !== "cameraUpdate");
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
for (const el of els) {
if (el.x == null) continue;
minX = Math.min(minX, el.x);
minY = Math.min(minY, el.y);
maxX = Math.max(maxX, el.x + (el.width ?? 0));
maxY = Math.max(maxY, el.y + (el.height ?? 0));
if (el.type === "arrow" && el.points)
for (const [px, py] of el.points) {
maxX = Math.max(maxX, el.x + px);
maxY = Math.max(maxY, el.y + py);
}
}
const pad = 28,
W = Math.ceil(maxX - minX + 2 * pad),
H = Math.ceil(maxY - minY + 2 * pad);
const ox = -minX + pad,
oy = -minY + pad,
f = (n) => +n.toFixed(2);
const rects = els
.filter((e) => e.type === "rectangle")
.sort((a, b) => b.width * b.height - a.width * a.height);
const texts = els.filter((e) => e.type === "text");
const arrows = els.filter((e) => e.type === "arrow");
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">\n`;
svg += `<defs><marker id="ah" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#555"/></marker></defs>\n`;
svg += `<rect width="${W}" height="${H}" fill="#ffffff"/>\n`;
for (const el of rects) {
const op = f((el.opacity ?? 100) / 100);
svg += `<rect x="${f(el.x + ox)}" y="${f(el.y + oy)}" width="${el.width}" height="${el.height}" rx="${el.roundness ? 8 : 0}" fill="${el.backgroundColor ?? "none"}" fill-opacity="${op}" stroke="${el.strokeColor ?? "#333"}" stroke-width="1.5"/>\n`;
}
for (const el of texts) {
const lines = el.text.split("\n"),
fs = el.fontSize ?? 14,
lh = f(fs * 1.45);
const cx = f(el.x + ox + (el.width ?? 0) / 2),
startY = f(el.y + oy + fs);
svg += `<text text-anchor="middle" fill="${el.strokeColor ?? "#333"}" font-size="${fs}" font-family="system-ui,sans-serif">\n`;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim() || "\u00a0";
svg +=
i === 0
? ` <tspan x="${cx}" y="${startY}">${esc(line)}</tspan>\n`
: ` <tspan x="${cx}" dy="${lh}">${esc(line)}</tspan>\n`;
}
svg += `</text>\n`;
}
for (const el of arrows) {
if (!el.points || el.points.length < 2) continue;
const pts = el.points
.map(([px, py]) => `${f(el.x + ox + px)},${f(el.y + oy + py)}`)
.join(" ");
svg += `<polyline points="${pts}" fill="none" stroke="${el.strokeColor ?? "#555"}" stroke-width="${el.strokeWidth ?? 2}" marker-end="url(#ah)"/>\n`;
}
return svg + `</svg>`;
}
for (const { input, output } of DIAGRAMS) {
if (!existsSync(input)) {
console.log(`Skip: ${input}`);
continue;
}
writeFileSync(output, toSvg(input), "utf-8");
console.log(`✓ ${output}`);
}
Run the script, then delete it:
bun docs/diagrams/_gen-svg.mjs || node docs/diagrams/_gen-svg.mjs
rm docs/diagrams/_gen-svg.mjs
Find the existing ## Architecture section in README.md (or append it before the last ## section if absent) and ensure it contains exactly:
## Architecture
### Folder Structure

### Data Flow

### System Architecture

Only update the image lines for diagrams that were regenerated in this run. Leave other lines untouched.
After completing all steps, report:
docs/diagrams/