Auto-generates code flow diagrams from Python module analysis. Detects when architecture diagrams become stale (code changed, diagram didn't). Use when: creating new modules, reviewing PRs for...
Automatically generate and maintain visual code flow diagrams across multiple programming languages. The skill auto-detects which languages are present in a target path, analyzes each one with a dedicated analyzer, and emits one mermaid diagram per language plus an optional combined high-level view. It also detects when committed diagrams are stale relative to the source they describe.
--combined view that places
each language in its own mermaid subgraph.normalize() function. No shared inheritance.| Language | Extensions | Analyzer | Parser | Notes |
|---|---|---|---|---|
| Python | .py |
python_analyzer |
ast |
Extracts import and from ⦠import ā¦. |
| TypeScript/JavaScript | .ts, .tsx, .js, .jsx, .mjs, .cjs |
ts_analyzer |
regex | Extracts import ⦠from, require(...), dynamic import(...). |
| Rust | .rs |
rust_analyzer |
regex | Extracts use crate::ā¦, use super::ā¦, mod ā¦. |
| Go | .go |
go_analyzer |
regex | Extracts single and grouped import declarations. |
Languages outside this table are skipped silently. See Extending below to add new ones.
amplifier-bundle/skills/code-visualizer/
āāā SKILL.md
āāā README.md
āāā scripts/
āāā __init__.py
āāā graph.py # Normalized data contract (Node, Edge, Graph)
āāā python_analyzer.py # normalize(paths) -> Graph
āāā ts_analyzer.py # normalize(paths) -> Graph
āāā rust_analyzer.py # normalize(paths) -> Graph
āāā go_analyzer.py # normalize(paths) -> Graph
āāā dispatcher.py # detect languages, route, return dict[lang, Graph]
āāā mermaid_renderer.py # render(graph) / render_combined(graphs)
āāā staleness.py # is_stale(target, diagram, languages)
āāā visualizer.py # CLI entry point
graph.py)@dataclass(frozen=True)
class Node:
id: str # mermaid-safe identifier
label: str # human-readable label (e.g. "src/auth/oauth.py")
language: str # "python" | "typescript" | "rust" | "go"
file_path: str # absolute path on disk
@dataclass(frozen=True)
class Edge:
src: str # Node.id of source
dst: str # Node.id of destination
kind: str # "import" | "require" | "use" | "mod" | "dynamic_import"
@dataclass(frozen=True)
class Graph:
language: str
nodes: tuple[Node, ...]
edges: tuple[Edge, ...]
Analyzers may import these dataclasses but must not inherit from any shared class. The data contract is the only coupling.
Each analyzer is a self-contained brick exposing exactly one entry point:
def normalize(paths: Iterable[Path]) -> Graph: ...
The function:
encoding="utf-8", errors="ignore".try/except and skips files that fail to parse.Graph whose language field matches the analyzer.The dispatcher uses a registry that maps language name ā extensions + module
name (string). It loads analyzers lazily via importlib.import_module so
adding a new language never requires touching the dispatcher's import
statements.
from scripts.dispatcher import analyze
graphs: dict[str, Graph] = analyze(target_path)
# {"python": Graph(...), "typescript": Graph(...)}
The dispatcher:
target_path with os.walk(..., followlinks=False).IGNORE_DIRS (.git, node_modules, .venv, venv, __pycache__,
dist, build, target, .mypy_cache, .pytest_cache, .tox).normalize() with its file list.dict[language_name, Graph] for languages that produced any
files.The renderer is language-blind:
from scripts.mermaid_renderer import render, render_combined
per_language: str = render(graph) # one diagram for one language
combined: str = render_combined(graphs) # one diagram, one subgraph/lang
Node IDs are sanitized ([^A-Za-z0-9_] -> _) and labels with quotes are
escaped to prevent diagram-syntax injection.
from scripts.staleness import is_stale
stale = is_stale(
target_path=Path("src/"),
diagram_path=Path("docs/architecture-python.mmd"),
languages=["python"],
)
Returns True if any source file with a matching language extension has an
mtime newer than diagram_path. Generalizes the previous Python-only
behavior.
The skill ships a single executable: scripts/visualizer.py.
python visualizer.py <path> [--output DIR] [--basename NAME]
[--check-staleness] [--combined]
| Flag | Default | Purpose |
|---|---|---|
<path> |
required | Directory to analyze. Must exist and be a directory. |
--output DIR |
./diagrams |
Output directory for .mmd files. |
--basename NAME |
architecture |
Filename stem. Validated against ^[A-Za-z0-9._-]+$. |
--check-staleness |
off | Print staleness report for existing diagrams; exit non-zero if stale. |
--combined |
off | Also write <basename>-combined.mmd containing all languages. |
| File | Contents |
|---|---|
<basename>-python.mmd |
Mermaid diagram for Python modules and their imports. |
<basename>-typescript.mmd |
Mermaid diagram for TS/JS files and their imports. |
<basename>-rust.mmd |
Mermaid diagram for Rust modules and use edges. |
<basename>-go.mmd |
Mermaid diagram for Go packages and import edges. |
<basename>-combined.mmd (with --combined) |
One diagram with one subgraph per detected language. |
Files are only written for languages that were actually detected.
python amplifier-bundle/skills/code-visualizer/scripts/visualizer.py . \
--output docs/diagrams --combined
Output (for this repo, which contains Python and JS):
docs/diagrams/architecture-python.mmd
docs/diagrams/architecture-typescript.mmd
docs/diagrams/architecture-combined.mmd
python amplifier-bundle/skills/code-visualizer/scripts/visualizer.py src/ \
--output docs/diagrams --check-staleness
# exits 1 if any per-language diagram is older than its source set
Provide a path that only contains files of one language; the dispatcher will
detect a single language and emit a single .mmd:
python visualizer.py src/auth/ # Python-only -> architecture-python.mmd
<path>, skipping IGNORE_DIRS and symlinks.--combined, the renderer composes one mermaid diagram with one
subgraph per detected language. Cross-language edges are not inferred in
the MVP.For a repo with:
src/api.py importing src/auth.pyweb/index.ts importing web/utils.tsarchitecture-python.mmd:
flowchart TD
src_api_py["src/api.py"]
src_auth_py["src/auth.py"]
src_api_py --> src_auth_py
architecture-typescript.mmd:
flowchart TD
web_index_ts["web/index.ts"]
web_utils_ts["web/utils.ts"]
web_index_ts --> web_utils_ts
architecture-combined.mmd:
flowchart TD
subgraph python ["python"]
src_api_py["src/api.py"]
src_auth_py["src/auth.py"]
src_api_py --> src_auth_py
end
subgraph typescript ["typescript"]
web_index_ts["web/index.ts"]
web_utils_ts["web/utils.ts"]
web_index_ts --> web_utils_ts
end
Note: the renderer emits the
subgraph <id> ["<label>"]form (space between id and bracketed label), which is the Mermaid-documented syntax accepted across recent Mermaid versions.test_mermaid_renderer.pypins the exact emitted form.
The skill follows the brick philosophy: a new language is a new self-contained module. There is no base class to subclass.
Create scripts/<lang>_analyzer.py with the entry point:
from collections.abc import Iterable
from pathlib import Path
from graph import Edge, Graph, Node # sibling import; works under `python visualizer.py`
def normalize(paths: Iterable[Path]) -> Graph:
nodes: list[Node] = []
edges: list[Edge] = []
for p in paths:
# parse file, append nodes/edges
...
return Graph(language="<lang>", nodes=tuple(nodes), edges=tuple(edges))
Register the language in scripts/dispatcher.py:
LANGUAGES = {
"python": {"exts": {".py"}, "module": "python_analyzer"},
"typescript": {"exts": {".ts", ".tsx", ".js", ".jsx",
".mjs", ".cjs"}, "module": "ts_analyzer"},
"rust": {"exts": {".rs"}, "module": "rust_analyzer"},
"go": {"exts": {".go"}, "module": "go_analyzer"},
# add here:
"<lang>": {"exts": {".ext"}, "module": "<lang>_analyzer"},
}
Add tests/test_<lang>_analyzer.py with tmp_path fixtures asserting
nodes and edges produced by representative source snippets.
Update the Supported Languages table above.
That's it. The renderer, dispatcher routing, staleness detector, and CLI all
work without further changes because they consume the language-blind Graph
data contract.
Tests live under amplifier-bundle/skills/code-visualizer/tests/ and run via
pytest. The skill registers its tests/ directory in the repo's
pytest.ini testpaths so CI picks them up automatically.
Test files:
| File | Purpose |
|---|---|
test_python_analyzer.py |
AST-driven import extraction; verifies edges for import/from. |
test_ts_analyzer.py |
import/require/dynamic import(); type-only and relative paths. |
test_dispatcher.py |
Mixed-language fixture; verifies correct routing per extension. |
test_mermaid_renderer.py |
Empty graphs, non-empty graphs, ID/label sanitization. |
test_staleness.py |
Mtime comparison across multiple language extensions. |
test_smoke_repo.py |
Runs dispatcher against the repo root; asserts non-empty mermaid |
| for both Python and TypeScript/JavaScript. |
Run only the skill's tests:
pytest amplifier-bundle/skills/code-visualizer/tests -q
exec/eval/
subprocess on analyzed files.<path> and --output are resolved with
Path.resolve() and rejected if non-existent or non-directory.--basename must match ^[A-Za-z0-9._-]+$.os.walk(..., followlinks=False) plus IGNORE_DIRS
prevents loops and escape.errors="ignore".[A-Za-z0-9_]; labels with
embedded quotes are escaped.--output
directory; source content is never logged.use {a, b::c}, Go cgo blocks). Documented per analyzer in source.import("...")/__import__ are not modeled..sh files are ignored.| Principle | How v2.0 follows it |
|---|---|
| Ruthless Simplicity | Stdlib-only; regex over tree-sitter; max-mtime over semantic diff. |
| Zero-BS | Real parsers (ast for Python, regex for others). Limitations documented honestly. |
| Modular Design | Each analyzer is a brick with a single normalize() stud. No inheritance. |
| Brick Composition | Renderer/dispatcher/staleness are independent bricks reusing only the data contract. |
The 1.x skill was Python-only. Forward-compatibility notes (verify against your actual 1.x integration before relying on them):
<basename>.mmd are now
<basename>-python.mmd. Update any references in README.md /
ARCHITECTURE.md.dispatcher.analyze(path) returning a dict[language, Graph]. Callers
that only want Python can use dispatcher.analyze(path)["python"].The skill automates what developers forget across all four supported languages: keeping diagrams in sync with code. It's not a compiler; it's a fast, honest, multi-language snapshot.