SVG Architecture Drawer (Smart Version)

SkillDev tools

Use when asked to draw system architecture diagrams, generate technical architecture SVGs, or export architecture diagrams to editable PowerPoint presentations. Supports multi-layer diagrams with automatic layout validation and scoring (16-dimension evaluator catches collisions, overlaps, dangles, crossings, palette issues incl. colorless and gray-dominant diagrams, low-contrast labels, misaligned peers, and a Step-1 design-brief contract: the declared palette/layout/flow is asserted against the rendered SVG).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the SVG Architecture Drawer (Smart Version) skill

What this skill tells your AI

The instructions your AI receives, as published by andy1314chen/architecture-drawer in plugins/architecture-drawer/skills/architecture-drawer/SKILL.md and read by ahel’s review.

This Skill converts complex technical descriptions into structured SVG architecture diagrams. It integrates layout constraints, collision detection, connection/arrow connectivity validation, and quality evaluation, automatically identifying and guiding the correction of layout errors. The script directory (referred to as $SKILL below) is this skill's own scripts/ folder. From a generator script that lives next to its artifacts, resolve it relative to the script's own location (never hard-code an absolute path, which breaks on other machines):

import os, sys
_HERE = os.path.dirname(os.path.abspath(__file__))
# From evals/<name>/gen.py -> ../../scripts ; adjust depth for your layout.
_SKILL = os.path.normpath(os.path.join(_HERE, "..", "..", "scripts"))
if _SKILL not in sys.path:
    sys.path.insert(0, _SKILL)

Fast Path (read this first; details below are on-demand reference)

Ordinary generation follows this bounded loop. Do not read the full specification sections before the first candidate runs — the checks below tell you what to fix, and each section explains its own vocabulary when a report line names it.

Strategy — the evaluator is the oracle, not your own verification. Do not read scripts/evaluator.py to internalize every threshold, and do not mentally verify the layout against all 16 checks before writing: that duplicates work the evaluator does in under a second. Sketch an approximate layout with sane coordinates, run gen.py, read the report lines, and fix exactly what they name (thresholds + repairs are tabulated in references/checks_cheatsheet.md — read that table instead of the evaluator source).

Do the arithmetic in code, not in your head. references/api_quickref.md tabulates every DSL signature + the known traps (which default draws an arrow, text-y semantics, container node_kind), and the layout helpers (layout_grid / layout_row / layout_band / layout_radial) compute positions from the array spec — state "6 chips, 3 per row, 24px gutters", not forty hand-derived coordinates. Start from the skeleton in references/gen_template.md (constants block for bands/nodes/flow, fixed evaluate-export tail) instead of re-deriving the boilerplate.

  1. Write the candidate — resolve $SKILL (snippet above), pick ONE palette preset from references/design_specs.md (do not invent hex values), land the Step-1 design-brief tokens as a constants block atop gen.py, draw with drawer.rect/circle/connect (register node_ids so connections validate), then immediately run the script. One clear main flow beats a dense map; ≤12 primary nodes before the first evaluation.
  2. Evaluateevaluate_svg(drawer) prints the score; every [FAIL] line names the defect class (dangle / route-through / crossing / text overlap / contrast / palette). Fix exactly what the lines name; the Auto-Correction section maps each tag to its repair.
  3. Bounded repair — at most 2 focused correction rounds on the highest-penalty FAILs (call auto_refine(drawer) first: gutter and spacing fix themselves). If the score reaches ≥80 with no [FAIL] remaining, ship. If two rounds do not converge, stop and report the unresolved [FAIL] lines truthfully — never claim success with defects outstanding, and never widen the canvas or shrink text to hide them.
digraph fastpath {
    "write candidate" -> "evaluate_svg()" -> "auto_refine + fix FAILs";
    "auto_refine + fix FAILs" -> "evaluate_svg()" [label="round ≤2"];
    "auto_refine + fix FAILs" -> "ship (score≥80, 0 FAIL)" [label="clean"];
    "auto_refine + fix FAILs" -> "report unresolved FAILs truthfully"
        [label="2 rounds spent"];
}

Step 0 — Intent Judgment (fidelity vs. completion)

Before writing any code, classify the requirement — the two failure modes are mirror images: transcribing a vague spec literally produces a broken diagram, and "improving" a precise spec produces one the user did not ask for.

Faithful mode — the description is clear and detailed (explicit components, relations, flow direction, canvas): transcribe it exactly. Do not invent components, layers, edges, or legend entries the user did not state, and do not "upgrade" the palette or topology on your own taste. Adding unrequested boxes is a defect, not a feature (画蛇添足).

Completion mode — the description is vague (the user may not have a fixed picture in mind yet): infer a reasonable design, then state what you inferred. Ambiguity signals and the corresponding conservative defaults:

Missing in the specConservative default
Relations between named componentsconnect adjacent tiers only, in the domain's natural flow (client → API → compute → storage)
Canvas / size1200×800 (or the diagram-type preset in references/diagram_types.md)
Diagram typepick from references/diagram_types.md by content keywords
Layer groupinggroup only when the spec's own vocabulary implies it ("… layer", "… module")
A composite-sounding component ("gateway", "engine")stays ONE node — never split into sub-nodes the user did not mention

Completion rule: an addition is legitimate only when the diagram is structurally incoherent without it — never decorative. List every assumption in the final reply ("assumed top-to-bottom flow; inferred gateway→auth edge") so the user can veto. When two readings are both plausible, pick the simpler one and note the alternative.

Step 1 — Design Brief (开工前完整设计)

After Step 0 classifies the requirement and BEFORE writing any code, produce a complete design proposal that combines the user's input.md with this skill's design system — state the design, then draw it. In an interactive session you may show the brief first and let the user veto; in headless/automation, print it in the reply and proceed.

The brief has five mandatory sections:

  1. Canvas & layout skeleton — canvas size, band/column/grid partition, margins, and the placement strategy as relative formulas (e.g. gap = (band_w − n·card_w) / (n + 1), y[i+1] = y[i] + h + GUTTER), not per-element hard-coded coordinates. Name what Step 0 left open (completion mode) or what the spec pinned (faithful mode).
  2. Palette — pick ONE preset scheme from references/design_specs.md (S1–S4) by information need and give the role→color mapping table: each business role gets a light tint fill PAIRED with its dark accent stroke; op cards stay white; accents ≤8; at least one chromatic accent (the ⑯ 无配色 floor). Exact hex from the spec, when given, wins over the preset. Color must OWN THE STRUCTURE, not decorate it: tint the band/container fills (band-style) or color the primary nodes (node-style) — a mostly gray/white skeleton with color confined to small chips FAILs the ⑯ 灰色主导 check (chromatic coverage <35% of elements AND <15% of area).
  3. Typography tiers — 3–4 tiers with concrete values (e.g. 20 / 14 / 12 / 10) and which text class uses each (title / section header / node label / note).
  4. Edge routing — flow directions, solid vs dashed semantics, spine/bus corridors routed OUTSIDE content areas (kiss container edges, never slice filled rects — the semantic-QA 箭头线盖在组件上 check), junction/merge points, and edge-label placement rules (off the line, perpendicular offset).
  5. Risk checklist — the pairings and budgets you expect to flirt with: text-on-fill contrast (⑮) for every tint+text pair, node spacing ≥14px, container gutter ≥20px, font-tier ratios ≥1.15×, marker ids actually used (marker 缺省陷阱).

Landing rule (常量落盘): the brief must not stay prose. It lands in TWO executable forms:

  1. a constants block at the top of gen.py (dimensions and tokens the drawing code reads), and
  2. a BRIEF = DesignBrief(...) contract object from $SKILL/design_brief.py — the machine-readable declaration the semantic-QA layer asserts the RENDERED SVG against (palette/layout/flow). The brief is the single source of truth and mutable during refine: if a contrast fix changes a tint, update BRIEF in the same round rather than silently deviating.
# --- Design Brief tokens (Step 1) — edit here, not scattered below --------
W, H       = 1240, 970                 # canvas
GUTTER     = 20                        # band spacing
INK, SUB   = "#1A1A1A", "#555555"      # text tiers 20/14/12/10
TINTS      = ["#D5E1EB", "#BBCEDF"]    # S1 layer fills (paired strokes below)
STROKES    = ["#1B3A5C", "#2563EB"]    # dark accent per tint
F_TIERS    = [20, 14, 12, 10]

from design_brief import DesignBrief, ColorSpec
BRIEF = DesignBrief(
    scheme="S1", layout="band", flow="top-down",   # layout: band|node; flow: top-down|left-right|none
    palette_role={                                  # key = data-node-id on the shape
        "api":    ColorSpec(TINTS[0], STROKES[0]),  # tinted container: fill+stroke PAIR
        "engine": ColorSpec(TINTS[1], STROKES[0]),
        "store":  ColorSpec("white",  STROKES[0]),  # plain op cards stay white
    },
    flow_chain=("api", "engine"),   # ordered pipeline stages ONLY — side
)                                   # bands / text-only bands stay out of the chain
# Render each palette key with a matching node_id= so the contract can
# attribute rendered shapes: drawer.rect(..., node_id="api", role="layer")

Contract rules (enforced by check_design_brief in semantic QA):

  • palette_role keys are data-node-id values — band layout: layer containers (role="layer"); node layout: primary nodes. One map, no duplicate layer list to drift.
  • flow_chain is the ordered pipeline (⊆ palette keys). Memory/cache side columns and text-only bands are palette members but NOT chain stages.
  • Declared tints rendered white → FAIL (structure lost its color); wrong tint/stroke or undeclared chromatic paint → WARN; ≥70% of inter-layer edges must follow the declared flow (return edges tolerated); chain first/middle/last layers need out/both/in ≥1.
  • Capability boundary: the checker verifies rendering ↔ self-declared contract consistency, not contract ↔ user intent — spec-entity coverage and human review of the brief guard the intent side.

Core Workflow: Generate-Evaluate-Correct

digraph eval_loop {
    "Generate gen.py" -> "evaluate_svg()" [label="run"];
    "evaluate_svg()" -> "score≥100 AND no [FAIL]?" [label="score"];
    "score≥100 AND no [FAIL]?" -> "Done" [label="yes"];
    "score≥100 AND no [FAIL]?" -> "auto_refine(drawer, max_iter=3)" [label="no"];
    "auto_refine(drawer, max_iter=3)" -> "score≥80?" [label="after n iterations"];
    "score≥80?" -> "Manual fix (coordinates/text)" [label="yes · ship"] ;
    "score≥80?" -> "Regenerate gen.py" [label="no · restart"];
}
  1. Content Parsing & Design Brief:

    • Identify layers, components, and flow direction.
    • Determine canvas dimensions (default 1200x800).
    • Write the Step 1 Design Brief (above) — layout skeleton, palette, tiers, edge routing, risks — BEFORE any drawing code; land its tokens as the gen.py constants block.
  2. Coding & Layout:

    • Write a Python script calling $SKILL/svg_utils.py.
    • Required: use drawer.check_collisions() to check overlaps; use the semantic API (below) to register nodes and edges so connections can be auto-validated.
  3. Quality Evaluation:

    • Call evaluate_svg(drawer) from $SKILL/evaluator.py.
    • Evaluation dimensions: ① Containment-aware element overlap detection (parent-child nesting does not count as a collision); ② boundary checks; ③ canvas coverage; ④ connection/arrow connectivity (endpoints must land on registered node borders, 12px tolerance; also detects degenerate zero-length edges and duplicate edges); ⑤ phantom anchor detection (nodes referenced by edges but invisible); ⑥ edge-routes-through-node (edges must not pass through the interior of a non-endpoint node, 3px inset); ⑦ edge crossing (two edge segments intersecting internally); ⑧ same-kind node minimum spacing (Euclidean distance between op/junction nodes ≥ 14px); ⑨ font-size tier detection (parse SVG to extract all font-size values, deduplicate to ≤4 tiers, adjacent tiers must be ≥1.15× apart — prevents accidental micro-steps like 11/12/13/14); ⑩ palette detection (accent colors ≤8 warning, ≤12 hard limit; coexistence of very dark L<0.2 and very light L>0.8 accents is flagged as a conflict; background defaults to light); ⑪ text overflow detection (parse all <text> elements' true geometry, estimate text width by font metrics: overflowing the canvas = FAIL, text wider than its container [smallest rectangle containing the text center] = WARN — closes the blind spot of text drawn via add_element that doesn't enter bboxes and is invisible to collision/boundary checks); ⑫ composition quality budget (ported from fireworks assess_composition: ≤2 bends per edge, path stretch ratio ≤1.35, container gutter ≥20px, shortest path segment ≥16px; text as a measurable obstacle — edge segments passing through a <text> bbox = FAIL, systematically eliminating "text crossed/covered by edges"; gutter is only checked for nodes fully contained within a container, cross-band nodes are not false-flagged). ⑨⑩⑪⑫ parse the actual SVG rather than relying on API calls, so they work equally well for raw add_element drawing.
    • text-vs-shape & text-vs-text overlap detection (check_text_overlaps): parses every <text> bbox (center model, dominant-baseline="central") against all visible circles/rects/polygons/lines/paths AND against other <text> — closes the registry blind spot where bbox=False text/shapes and add_element shapes are invisible to check_collisions. Legend/background shapes, the full-canvas bg rect, and rects fully containing the text (intentional in-box labels) are exempt. Like ⑨⑩⑪⑫, this parses the actual SVG rather than trusting the API.
    • same-kind peer alignment (check_alignment): two SAME-SIZED same-kind visible nodes that read as a row or column (strong overlap on the perpendicular axis) yet share NEITHER a top/bottom/left/right edge (within 5px) NOR a center line (within 15% of the shorter side) are flagged — the "align to shared edges" layout principle. Differently-sized peers are skipped (a row of varied components legitimately staggers).
    • text-on-fill contrast (check_contrast): WCAG 2 contrast ratio between each <text>'s fill and the fill of the smallest <rect> containing it — FAIL below 3:1 (large-text floor), WARN below 4.5:1 (AA for normal text) / 3:1 large (≥24px, or ≥18.5px bold). Only text on a non-neutral (accent) fill is measured; accent-colored text on a white/neutral canvas (category labels, captions) is a typographic choice, not a fill defect, and is skipped. Replaces the former "manual review recommended" placeholder.
    • chromatic palette floor & gray-dominance (check_palette): at least one accent must carry a readable hue (HSL saturation ≥0.25) — a diagram whose only "accents" are desaturated slate tones (#546E7A et al.) or none at all is effectively colorless (无配色) and FAILs. And color must own the structure, not just decorate it: when chromatic shapes cover <35% of business elements AND <15% of painted area, the diagram is gray-dominant (灰色主导) — neutral bands/containers with color confined to small chips — and also FAILs. One strong axis is a legitimate scheme: band-style diagrams ride the area axis (tinted container fills), node-style diagrams the element axis (colored primary nodes). Pastel tints (#DAE8FC) count as chromatic; slate/pure grays do not. The classic trigger for both: "fixing" a contrast WARN by de-coloring.

    3b. Semantic QA (after the geometry score): call run_semantic_qa from $SKILL/semantic_qa.py. The evaluator above checks how the picture renders; this checks what the picture means — the three defect classes a bounding-box evaluator structurally cannot see:

    • marker 缺省陷阱marker-end="url(#X)" referencing an undefined <marker id> (the classic case: arrow_head("arrow", ...) registered but a connect() call left at its default marker_end="arrowhead") → every arrowhead on that edge silently vanishes. FAIL. A defined-but-never-used marker (usually a forgotten marker_end=) is flagged as WARN.
    • FIGS 尺寸漂移 — declared canvas vs. actual content bbox: content far smaller than the canvas (mis-sized diagram), content poking outside (clipped), or a mismatch against the design-spec size passed as expected_size=(w, h).
    • 标签错位 — a centered label off its node's centre, a label floating in whitespace (not inside, near, or beneath any node — legitimate top-band titles, branch labels beside edges, and cluster captions are exempt), or a business node box with no label at all.
    • 箭头线盖在组件上 (rail-slices-container / connector-through-card) — parsed straight from the rendered geometry, role-blind to the registry: a raw-line() bus rail that slices through filled band containers (the right-spine-at-x≈776-inside-the-band trap), or a connector crossing a business card's interior. The registry evaluator is structurally blind to both (rails are never registered as edges; role='layer' containers are never registered as nodes).
    • 文本语义 (check_text_semantics, pass spec_text=input.md) — placeholder/garbled/empty <text> → FAIL; spec component identifiers (bold/backtick identifiers like AgentEvent, server_queue) missing from the diagram: coverage <40% → FAIL (regenerate — whole components lost), 40–85% → WARN (paraphrase advisory fed back into refine rounds).
    from semantic_qa import run_semantic_qa
    
    score, report = evaluate_svg(drawer)          # geometry first
    spec = Path("input.md").read_text() if Path("input.md").exists() else None
    qa = run_semantic_qa(drawer, expected_size=(1240, 970), spec_text=spec,
                         brief=BRIEF)             # + the Step-1 contract
    for line in qa.report():
        print(line)
    # qa.has_fail → semantic defect (dangling marker ref, rail over a
    # component, lost spec entities, brief-contract violation): fix before export
    # brief omitted → brief-absent WARN: declaring the contract is not optional
    
  4. Auto-Correction:

    • If the evaluation score is below 80, analyze the [FAIL] items in the report.
    • Connection issues (dangles / Degenerate edge / overlaps): use drawer.connect(...) to let endpoints auto-snap to node borders; avoid manually computing offset coordinates.
    • phantom (phantom anchors): the node referenced by an edge is invisible → give it a real fill/stroke, or use the distinct-port pattern to connect to a visible junction.
    • routes through node: an edge cuts through an intermediate node → reroute via orthogonal bypass channels, or relay through a junction (see distinct-port pattern), keeping a ≥20px gap from the intermediate node.
    • cross (edge crossings): adjust node layout or routing channels so edges don't intersect (reference fireworks' zero-crossing budget).
    • too close: same-kind nodes are clustered → increase spacing or enlarge the canvas.
    • Arrow position: connect() auto-retracts by marker_tip_depth — retraction = (markerWidth − refX) × stroke_width, derived from the marker dimensions registered by arrow_head(), so the arrow tip lands exactly on the target border (neither poking in nor leaving a gap). For custom markers, pass the real dimensions via arrow_head(id, color, marker_width=, ref_x=) — no manual tweaking needed.
    • font (font sizes): more than 4 distinct tiers after dedup → converge to 3-4 tiers (title/body/note); near-overlapping tiers (ratio <1.15, e.g. 11/12) → merge into one tier. Recommended modular scale: 20 / 14 / 12 / 10 (all steps ≥1.15). This matches the tier count measured in each ink-graph style.
    • palette: accent count >8 → trim toward a preset scheme (S1–S4) — consolidate near-hue accents, drop redundant category colors; >12 → same, harder. no chromatic accent (无配色, FAIL) → restore tinted layer fills + accent strokes from a preset scheme. gray-dominant (灰色主导, FAIL) → color is marginal: tint the band/container fills (band-style) or color the primary nodes (node-style) so the scheme owns the skeleton — do not merely enlarge a legend/chip. Never satisfy a palette or contrast finding by reverting the whole diagram to neutral — that trades a WARN for a colorless or gray-dominant diagram, which now FAILs. Luminance conflict (very dark + very light coexist) → unify into one brightness family. Non-light background → apply white by default; dark themes must declare set_background()/bg=. See references/design_specs.md for the 4 preset schemes (S1–S4) and when to use each.
    • Layout issues: adjust component coordinates, spacing, or scale ratio, then regenerate.
    • text overflow: text exceeds the canvas → shorten the copy or shift the start point left; text wider than its card/container → shorten, auto-wrap by container width (greedy word-wrap), or widen the container. Note that <text> does not enter bboxes by default, so collision/boundary checks can't see it — this detection fills that gap.
    • text overlap (text on a shape or another text): a label sits on top of a circle/triangle/arc/line or collides with a neighboring label → move the label clear of the shape (place it above/below the icon, not on it) or shorten it. auto_refine cannot fix this (no geometry handle for raw add_element text) — adjust coordinates manually. This catches overlaps check_collisions misses because bbox=False text/shapes and add_element shapes bypass the collision registry.
    • contrast (low text-on-fill contrast): a label doesn't read against its accent card (ratio <3:1 FAIL, <4.5:1 WARN for normal text) → darken/lighten the text fill toward the channel extreme (pure #000000/#ffffff on a mid-tone card is always safe), or switch the card to a lighter tint of the same hue so a dark label clears AA. De-coloring the card to white/gray is NOT a fix — it silences this check by making the diagram colorless, which the ⑯ chromatic floor then FAILs; always keep a tint fill paired with its dark accent stroke. Note: accent-colored text on a neutral canvas is a deliberate category/heading choice and is not flagged — only labels on accent fills are. auto_refine does not touch colors; adjust manually.
    • alignment (misaligned same-size peers): two same-sized same-kind nodes in a row/column share no edge/center line → nudge one onto the other's top/bottom (row) or left/right (column) edge, or onto a shared center line. Differently-sized peers are exempt (they legitimately stagger). auto_refine does not handle alignment yet — adjust coordinates manually.
    • composition gutter (insufficient container margin): node too close to the container edge → push the node toward the container center; or call auto_refine(drawer) (below) to iteratively auto-correct.
    • auto_refine(drawer, target_score=100, max_iter=3): reads the evaluate_svg report and auto-corrects programmable issue categories (gutter → nudge node toward container center; too close → spread along the primary axis), looping until the target is met or iterations are exhausted. Returns (score, report, fixes). Complex fixes (dangles/cross/route-through) still need manual intervention — auto_refine only handles geometric micro-adjustments.

Node & Edge Semantics

For the evaluator to "see" connections, drawing code must register connectable rectangles as nodes and connections as edges:

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
39
Forks
2
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
architecture-drawer
Source
github.com/andy1314chen/architecture-drawer