Skip to content

tld analyze

View Markdown

tld analyze reads your source code and generates architecture diagrams from it. It’s a one-shot command: scan, discover, write.

Terminal window
tld analyze .

Under the hood, tld analyze runs a pipeline:

  1. Parse: Tree-sitter grammars parse every source file into abstract syntax trees. Each supported language has its own grammar, so the parsing is language-aware, not regex-based.

  2. Extract: The parser extracts named symbols , functions, classes, structs, interfaces, modules. It also extracts references , imports, function calls, method invocations, type annotations.

  3. Filter: Noise filters remove test files, generated code, and boilerplate that would clutter the diagram.

  4. Cluster: Components are grouped by heuristics. Related files in the same package become a single element. Dependencies between packages become connectors.

  5. Threshold: Density controls prevent “hairball” diagrams. If a utility is imported by 50 files, it gets collapsed into a group rather than generating 50 connector lines.

  6. Write: The results go into your workspace YAML files (elements.yaml, connectors.yaml).

LanguageExtensionsTree-sitter grammar
Go.gotree-sitter-go
TypeScript / JavaScript.ts, .tsx, .js, .jsxtree-sitter-typescript
Python.pytree-sitter-python
Java.javatree-sitter-java
C++.cpp, .hpp, .cc, .htree-sitter-cpp
Rust.rstree-sitter-rust

Open an issue or even better, open a PR :). Tree-sitter supports 150+ languages, it is relatively easy to add new ones but might take time to polish.

Terminal window
# Scan everything
tld analyze .
# Scan a specific directory
tld analyze ./src
# Scan only Go files
tld analyze . --language go
# Multiple languages
tld analyze . --language go --language python

Dense codebases produce dense diagrams. These flags control how much detail you get:

Terminal window
tld analyze . \
--max-elements-per-view 30 \
--max-connectors-per-view 60 \
--max-incoming-per-element 10 \
--max-outgoing-per-element 10
FlagDefaultWhat it does
--max-elements-per-view50Cap on elements in a single view
--max-incoming-per-element15Collapses connections if an element receives too many
--max-outgoing-per-element15Collapses connections if an element sends too many
--max-expanded-connectors-per-group30Merges file-level connectors into folder-level ones

The defaults are reasonable for most projects. If output diagrams look sparse, raise the limits. If they’re noisy, lower them.

When an element exceeds a threshold, the connectors are collapsed into a group connector. Instead of 50 individual lines to a utility file, you get one group connector saying “50 connections to utils/”. The tooltip shows the details. The diagram stays clean.

Preview changes without modifying files:

Terminal window
tld analyze . --dry-run

Shows what would be added, updated, or removed. Nothing is written.

Detect when code and diagrams have diverged:

Terminal window
tld analyze . --dry-run --fail-on-drift

Returns exit code 1 if the code has elements or connections not yet reflected in the diagrams. Designed to be suitable for CI, fail the build if architecture docs are stale.

Machine-readable results:

Terminal window
tld analyze . --format json
{
"elements_found": 42,
"connectors_found": 89,
"drift": [
{"type": "new_element", "ref": "payment-svc", "reason": "found in source but not in YAML"}
]
}
Terminal window
# Validate diagrams match code
tld analyze ./src --dry-run --fail-on-drift
# Specific language + density limits for cleaner output
tld analyze ./pkg --language go --max-elements-per-view 50
# Machine-readable for custom reporting
tld analyze . --format json

tld analyze is non-destructive. It adds new elements and updates existing ones, but never removes things you’ve manually documented. If you hand-crafted a beautiful diagram and the code scan finds additional services, it adds them alongside your work.

This is by design. Static analysis can’t understand intent. It finds what’s in the code, but you know what’s in the architecture. The two should complement each other.

Sometimes the scanner finds things that don’t match your mental model. Maybe it grouped things differently than you would. Maybe it found a dependency that doesn’t actually exist at the architecture level.

tld analyze is one-shot. Ideal for:

  • Initial diagram from an existing codebase
  • Periodic sync before reviews
  • CI checks for drift

tld watch is continuous. It stays running, monitoring changes. Ideal for:

  • Seeing live impact of code changes on architecture
  • Keeping diagrams in sync during active development

They use the same scanning engine under the hood.

For semantic grouping beyond folder structure:

Terminal window
tld analyze . --embedding-provider openai --embedding-model text-embedding-3-small

With embeddings, related components cluster together based on what they do, not just where they live. A validation.go file and a validator.py file might share nothing structurally but serve the same purpose, embeddings allow tld to catch this. Also helps with refactoring and renaming symbols.

Requires an OpenAI compatible endpoint. Configure via global config or environment variables.