Help us improve
Share bugs, ideas, or general feedback.
From tldiagram
Create architecture diagrams from a codebase using the tld CLI. Use this skill whenever the user asks to diagram, map, or document their codebase or system architecture — even if they don't mention "tld" or "diagram" explicitly. Trigger on phrases like "map my services", "document my architecture", "create a system diagram", "diagram this repo", "show how my code is structured", or any request to visually represent how a system's components fit together.
npx claudepluginhub mertcikla/tld-cli --plugin create-diagramHow this skill is triggered — by the user, by Claude, or both
Slash command
/tldiagram:create-diagramThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Reference:** When you need exact command syntax, flags, or a full walkthrough example, read `references/tld-docs.md`.
Creates p5.js generative art with seeded randomness, noise fields, and interactive parameter exploration. Use for algorithmic art, flow fields, or particle systems.
Share bugs, ideas, or general feedback.
Reference: When you need exact command syntax, flags, or a full walkthrough example, read
references/tld-docs.md.
In tld, everything is an element — a service, a database, a person, a class, a method, a module. Any element can have a view: a navigable canvas that holds child elements. Navigation works by drilling into any element whose view contains something interesting. There are no standalone "diagrams" to create separately — views emerge from the element hierarchy.
Connectors express relationships between elements in the same view. The view is inferred automatically from the elements' shared parent.
Roles (service, database, external system, person, etc.) are expressed as tags — not as distinct types. All elements share a single kind.
This is not traditional architecture diagramming. It is a navigatable atlas of the entire system — like a hyperlinked wiki where you can zoom into anything. At the top you see services talking to each other. Drill into a service and you see its modules. Drill into a module and you see its classes. Drill into a class and you see its methods, parameters, what each method calls, its parent classes, its subclasses.
The goal at full detail: a reader can start at the root view and follow drill-downs until they understand any piece of the system without ever opening a file.
A view is only useful if it tells you something you couldn't immediately see from the file structure. The failure mode to avoid is a shallow view — a list of boxes with no connectors, or connectors with no labels, or elements that exist in isolation without showing who talks to whom, who owns what data, or where failures propagate. Boxes are cheap. The connectors are the insight.
Before moving to the next step, ask: would a new engineer reading this view understand how data or control actually flows? If not, add more connectors.
All tld commands go into a single bash script, diagram.sh, in the workspace directory. The script is your notes and your execution log — comments explain what you found, commands record what you built.
Create the script once at the beginning of Step 3:
#!/bin/bash
# Architecture diagram build script
set -e
For every batch:
# === comment header) to diagram.shThe script is the complete, replayable record of every diagram decision. Never run a tld command outside it.
Run this check after every batch of tld add or tld connect commands:
Append any missing connectors to diagram.sh and run them before moving on.
Keep each view focused. Aim for ~15 elements per view; never exceed 25. A crowded view isn't more informative — it becomes a wall of boxes where the relationships disappear. If you're placing more than 15–20 elements in a single view, that's a signal to use tags, cluster, or both.
Before clustering, consider whether tags can give the view enough structure to remain readable at a higher element count. Tags let a reader filter the view to a single plane, role, or boundary — making ~20 elements navigable where a flat list would not be.
Tags are applied at element creation time with --tag (repeatable):
tld add "Auth Service" --ref auth-svc --parent backend \
--tag boundary:internal --tag protocol:grpc
Good uses of tags:
layer:api, layer:data, layer:infraboundary:public, boundary:private, boundary:internal, boundary:externalprotocol:grpc, protocol:rest, protocol:eventrole:service, role:database, role:worker, role:gatewayConstraints — tags are not free:
Tags complement clustering — use both. Tags handle role clarity within a view; clustering handles size. A well-tagged view of 18 elements is better than a cluster of 8 that hides the relationships. A cluster of 5 with clear tags on each element is better than a flat list of 18 tagged elements where the groupings are invisible.
After mapping your inventory, count the elements you're about to place. If the count is heading above 15–20 even after considering tags:
tld add "Auth Services" --parent root. This element's view will hold the cluster.tld add "Token Validator" --parent auth-services.The goal is not to hide complexity — it's to present the right level of detail at each zoom level.
Copy this checklist and check off items as you complete them:
tld validatetld plan and give feedback; iterate if neededBefore exploring, ask the user two questions:
- How many views do you expect? (rough target is fine — e.g. "around 20", "as many as needed", "keep it under 10")
- How deeply nested do you want the drill-downs? (e.g. "just top-level", "2–3 levels", "go as deep as possible")
Use their answers to calibrate the rest of the work. If they don't know, suggest options based on the codebase size once you've done a quick directory scan.
Shallow ~5–10 views, 1–2 levels deep — Services and their direct dependencies. Entry points, major layers, external systems. Nothing low level.
Medium ~10–30 views, 2–3 levels deep — Modules and packages decomposed. Key classes identified and placed. Major data and logic flows wired. Inheritance shown where architecturally significant.
Detailed ~50–200+ views, 4–6 levels deep — Every significant class has its own linked view. Each class view shows: __init__ parameters and their types, all public methods with connectors showing which methods call which, all private methods, the full inheritance chain. Every significant function shows its parameters, return type, and external calls. A reader should be able to navigate from the root view down to understanding a specific method's behavior without opening a file.
Start broad, then narrow:
main.go, app.tsx, index.ts, server.py, etc.The output of this step is not a list of files — it's a map of connections. For each component you identify, note: what does it call? what calls it? what data does it read or write?
Before creating a single element, write out an explicit inventory of every major subsystem you found:
Subsystem inventory:
- [name]: [one-line description] | calls: [...] | called by: [...] | shared deps: [...]
- [name]: ...
Do not proceed to Step 3 until every top-level directory or package is accounted for. A diagram that covers 60% of the codebase is worse than no diagram, because it creates false confidence.
Create diagram.sh, then append and run the root elements as the first batch. Root elements appear on the top-level canvas:
# === Root elements ===
tld add "Domain & Business Logic" --ref domain --tag layer:domain
tld add "Data & Persistence" --ref data --tag layer:data
tld add "Interfaces & Integrations" --ref interfaces --tag layer:interfaces
tld add "Platform & Infrastructure" --ref deployment --tag layer:infra
Append and run the next level as a second batch:
# === Level 2: major subsystems ===
tld add "Backend" --ref backend --parent domain --tag layer:api
tld add "Frontend" --ref frontend --parent interfaces --tag boundary:public
tld add "Storage" --ref storage --parent data --tag role:database
Batch checkpoint: Are there connectors between root elements suggested by your inventory? Add them now:
tld connect domain data --label "reads/writes"
tld connect interfaces domain --label "calls"
Work one view at a time. For each view (parent element), append its children as a batch, run it, then immediately append and run its connectors before moving to the next.
# === Backend children ===
tld add "REST API" --parent backend --technology "Go" --ref api --tag protocol:rest --tag boundary:public
tld add "Stripe API" --parent backend --technology "Stripe" --ref stripe --tag boundary:external
tld add "Job Worker" --parent backend --technology "Go" --ref worker --tag role:worker
Batch checkpoint after elements: Do any of these elements also belong in other views? Add those placements now.
Append and run connectors for the same view immediately after its elements. View is inferred automatically:
# === Backend connectors ===
tld connect api stripe --label "billing"
tld connect api db --label "reads/writes"
tld connect worker queue --label "consumes jobs"
Labels should describe what the interaction does — "validates JWT", "publishes events" — not just "calls".
An unconnected element is a bug — either it's missing connectors, or it doesn't belong in this view.
Batch checkpoint after connectors: Any elements from other views that interact with elements you just wired? Append cross-view connectors now.
Count elements in each view. More than ~15? First consider whether tags can organize the view into readable planes (e.g. layer:api vs layer:data, or boundary:public vs boundary:private). If the count still exceeds 20 after tagging, apply the clustering strategy from the View density section. Use both together when the view warrants it.
Identify every element appearing in more than one view (databases, caches, queues, external APIs). For each one, verify:
No connectors on a shared element = missing information. Append any missing placements and connectors to diagram.sh and run them.
Depth target: match the user's requested level. At full detail, every significant class and module has its own view and the depth can reach 5–6 levels. Stop only when there is nothing meaningful left to decompose.
Example depth for a typical backend service at full detail:
Root (L1)
└── Backend Service (L2)
└── Auth Module (L3)
└── AuthService class (L4)
└── AuthService: __init__ params, methods, base classes, subclasses (L5)
└── validate_token() : params, JWT lib call, DB call, return type (L6)
Class view (L4–L5): one element per method (public and significant private), one element per __init__ parameter that is a dependency (not primitives), connectors showing which methods call which, connectors to parent class element and each known subclass element.
Inheritance view: a dedicated view for a polymorphic hierarchy — base class at top, all concrete subclasses below, connectors labelled with what each override changes.
Function/method view (L5–L6): parameters as elements (with types), return type as an element, connectors to external calls it makes. Only create this level if the function is complex enough to warrant it — more than ~5 distinct operations.
Module/package view (L3–L4): exported symbols as elements, internal dependencies between them wired, external imports shown as elements.
For every subsystem, append one batch per view and run it before starting the next:
# === API (L3) ===
tld add "API" --ref api-internals --parent backend
No separate link command needed — drilling into api-internals from the backend view happens automatically.
Go back to the code — don't guess. Apply the 15-element rule here too; use tags and cluster if needed.
tld add "Auth Middleware" --parent api-internals --technology "Go" --ref auth-mw --tag boundary:internal
tld add "User Handler" --parent api-internals --technology "Go" --ref user-handler --tag protocol:rest
tld add "Database" --parent api-internals --technology "PostgreSQL" --ref db --tag role:database
If
dbalready exists from a parent view,tld addwith the same ref but a new--parentadds a new placement rather than a duplicate. Reused elements don't inherit connectors — add them explicitly for this context.
tld connect auth-mw user-handler --label "forwards request"
tld connect user-handler db --label "SQL"
Before moving to the next subsystem, check every element: at least one incoming connector, at least one outgoing connector, labels specific enough to tell a reader what the interaction does. Missing connectors = go back to the code.
Batch checkpoint: Do any elements in this view appear in sibling views? Are there connectors that cross view boundaries? Append and run them now.
Repeat 6a–6c for each remaining subsystem, going 3–5 levels deep per topic.
Append to diagram.sh and run:
# === Validation ===
tld validate
Carefully observe its output and its instructions to improve the quality of the diagrams. Don't use scripting to automate this and just to bypass the validation. It needs careful attention and deliberation to improve the diagrams. Work on each issue one by one.
Fix any broken refs or missing fields, append the fixes to diagram.sh, and re-run.
Ask the user to run:
tld plan
Walk them through the output. If they want changes — more elements, deeper drill-downs, better connector labels — append the changes to diagram.sh, run the new block, and iterate.