01 · Install
Add enola. to your agent
enola is a Go binary. Build it once, point your agent's MCP config at it, and every MCP-compatible tool in your stack gets access to the architectural graph.
curl -fsSL https://raw.githubusercontent.com/enola-labs/enola/main/install.sh | sh
claude mcp add enola enola /path/to/mcp-arch.yaml
{
"mcpServers": {
"enola": {
"command": "enola",
"args": ["/path/to/mcp-arch.yaml"]
}
}
}
go build -o enola ./cmd/enola
# or install globally
go install ./cmd/enola
02 · Workflow
Generate first, then ask
Enola. is a first step, not a replacement for file reading. Generate the snapshot before your agent starts exploring — then every prompt in the session has the map as context.
Generate the snapshot
Ask your agent: "Generate an architectural snapshot of /path/to/project". This runs the full pipeline. Even large repos index in seconds.
Ask structural questions naturally
You don't need to reference enola explicitly. The agent reads the arch://snapshot/context resource automatically and answers from the map.
Regenerate after major changes
If you add packages, rename modules, or restructure directories, call generate_snapshot again. Incremental updates are fast — only changed files are re-extracted.
03 · The graph
Kinds and relations
Enola. models your codebase as a typed, directed graph. The nodes are architectural kinds — the things in your code. The edges are relations — how those things connect. Every tool operates on this graph.
Kinds (nodes)
| Kind | What it represents |
|---|---|
| module | A package or directory — the coarsest grouping unit. |
| symbol | A function, method, struct, interface, type, class, or constant. |
| route | An HTTP or API endpoint — method, path, and handler. |
| storage | A database table or data store (e.g. ActiveRecord model, JPA entity). |
| dependency | An import relationship to an external or internal package. |
| service | A whole repository — used when analyzing multiple repos together. |
Relations (edges)
| Relation | Meaning |
|---|---|
| declares | A module declares a symbol, route, or storage entity. |
| imports | One module imports another. |
| calls | A function or method invokes another. |
| implements | A type implements an interface or mixes in a module. |
| depends_on | A general dependency edge between any two nodes. |
| instantiates | A function creates an instance of a type. |
| injects | A DI framework provides a dependency (Hilt, Spring, constructor injection). |
| has_method | A class or struct owns a method — synthetic edge for traversal completeness. |
04 · MCP tools
What enola. exposes to agents
Once connected, your agent sees thirteen tools. Most sessions only need generate_snapshot — the traversal, insight, and diff tools handle deeper follow-ups.
| Tool | What it does |
|---|---|
| generate_snapshot | Parses the codebase and produces a structured architectural graph. Auto-detects Go, TypeScript, Kotlin, Ruby, Python, Swift, Rust, C, C++, Java, PHP, and OpenAPI from project markers — no per-language config needed. Extracts module, symbol, dependency, route, and relation facts. Pass append: true to merge multiple repositories into a single traversable graph. Pass fresh: true when switching to a different project in the same session — required to avoid silently reusing stale graph state. |
| query_facts | Precision filter over extracted facts. Best for targeted lookups when you know what you want. Filter by kind (module, symbol, function, class, route, dependency), name (substring), file, relation, prop, or repo. Output modes: compact (token-efficient), full (all fields), names (identifiers only). Use explore instead when you want a broad view of an entity. |
| explore | Primary post-snapshot navigation tool. Pass any module name, file path, symbol, or directory and get a structured markdown overview of its contents, dependencies, relations, and outbound HTTP and gRPC client calls (Retrofit, URLSession, fetch, Net::HTTP, http.Client, grpc-go, connect-go, grpc-web, connect-es, Python gRPC stubs). Accepts partial names — path normalization handles trailing slashes and relative paths. Reach for this first; use query_facts for structured filtering or traverse for deep graph walking. |
| show_symbol | Return source code for a named symbol. Tries exact match first, falls back to substring. Default context window: ~15 lines before, ~45 lines after the definition. Override with context_lines_before and context_lines_after. Works across multi-repo snapshots. |
| traverse | Walk the dependency or call graph from a starting node. direction='forward': what does X import or depend on? direction='reverse': what depends on X? Starting node accepts substring — multiple matches are grouped by match. Filter by relation kind (e.g. imports, calls, implements) or node kind (e.g. module, function). Default depth: 3. |
| find_path | BFS shortest path between two graph nodes. Both from and to accept substring names — if multiple nodes match, the closest pair is used. Answers "how does X reach Y?" or "what is the call chain from main to this handler?". Returns an ordered list of node → relation → node steps. |
| impact_analysis | Return the blast radius of a change — all nodes that transitively depend on the target, grouped by depth. Set include_forward: true to also return what the target depends on (reverse + forward in one call). Target accepts substring matching. Use before refactoring to understand risk surface. |
| query_insights | Retrieve computed explainer findings — coverage gaps, unused routes, architectural smells, complexity outliers — filtered by source, repo, or confidence threshold. Use after generate_snapshot to surface what the insight layer found without reading the full insights.json. |
| coverage_report | Return a cross-repo edge resolution summary — which outbound calls were matched to handlers in other repositories and which remain unresolved. Useful for verifying that multi-repo graphs are correctly linked before running impact analysis. |
| set_baseline | Pin the current snapshot as a baseline. The baseline is stored in .enola/baseline/ and persists across re-snapshots. Use before a major refactor to enable before/after comparison. |
| diff_snapshot | Compare the current snapshot against the pinned baseline and return a structural delta — nodes added or removed, edges changed, new cycles or layer violations introduced. Useful for verifying that a refactor produced only the intended structural changes. Guarded by a comparability check — mismatched configs or extractor versions are flagged instead of silently diffed. |
| snapshot_receipt | Return the receipt.json for the current snapshot — provenance metadata (enola version, git ref, content fingerprint, config hash, output hash) plus extraction-quality metrics. Use to verify exactly what a snapshot was built from before trusting its output downstream. |
| compare_receipts | Compare two snapshot receipts and report whether they're comparable — same config hash, extractor versions, and repo set. Run before diff_snapshot to catch mismatches that would otherwise make a structural diff misleading. |
05 · Languages
Supported languages
Enola. auto-detects languages from project markers and runs the appropriate extractor. No per-language config required.
| Language | Extractor | Detected by |
|---|---|---|
| Go | go/ast | go.mod |
| Java | tree-sitter AST parser | pom.xml, build.gradle, or .java files (Spring routes / JPA / Lombok DI / Dubbo SPI aware) |
| TypeScript / JS | tree-sitter | tsconfig.json or package.json — processes .ts, .tsx, .js, .jsx (Next.js & monorepo aware) |
| Vue / Nuxt | tree-sitter (TS extractor) | package.json with vue or nuxt dependency — SFC script block extraction; Nuxt file-based routes inferred from pages/ |
| Svelte / SvelteKit | tree-sitter (TS extractor) | package.json with svelte dependency — .svelte SFC extraction; $lib alias resolved to src/lib/; SvelteKit file-based routes inferred |
| Python | tree-sitter AST parser | pyproject.toml, setup.py, requirements.txt, or similar (FastAPI / Django / SQLAlchemy / gRPC aware) |
| Kotlin | tree-sitter AST parser | build.gradle.kts, build.gradle, or pom.xml (Compose / Hilt / Room aware; falls back to a src/main/kotlin source root) |
| Swift | tree-sitter AST parser | Package.swift, .xcodeproj, or .xcworkspace (SwiftUI / UIKit aware) |
| Ruby | tree-sitter AST parser | Gemfile (Rails / ActiveRecord / Packwerk aware), or extensionless scripts with a Ruby shebang for Gemfile-less repos |
| C | tree-sitter AST parser | .c / .h files |
| C++ | tree-sitter AST parser | CMakeLists.txt, .cpp / .hpp / .cc files (header/source merging, namespaces, templates) |
| PHP | tree-sitter AST parser | composer.json or .php files (WordPress / Laravel / Symfony aware) |
| Rust | tree-sitter AST parser | Cargo.toml (cross-crate and submodule resolution for Cargo workspaces, Axum route detection) |
| OpenAPI | YAML/JSON scanner | Any file containing openapi: or swagger: |
06 · Configuration
mcp-arch.yaml
Create an mcp-arch.yaml in your repository root (or pass a custom path as the first argument to the binary). All fields are optional.
| Field | Description | Default |
|---|---|---|
| repo | Repository root path | "." |
| ignore | Glob patterns for files/dirs to skip | vendor, node_modules, .git, tests, build dirs, docs |
| extractors | Enabled extractors | All supported |
| explainers | cycles, layers, cross-repo, coverage, unused-routes, god-class, hotspots, dependency-depth, exported-surface, complexity-outliers | All |
| output.dir | Output directory for generated artifacts | ".enola" |
| output.max_context_tokens | Token budget for the LLM context summary | 16000 |
| incremental | Reuse content-hash cache to skip unchanged files on re-snapshot | true |
repo: "." extractors: - go - typescript - python output: dir: ".enola" max_context_tokens: 16000
07 · CLI modes
Running enola. outside an agent
Besides running as an MCP server, enola has standalone modes for use directly from the terminal.
One-shot snapshot
Build or refresh the architectural graph without starting the MCP server. Artifacts are written to the configured output.dir (default .enola/).
Human-readable report
Generate a snapshot, compute statistics over the fact graph, and print a report to stdout — no MCP server started, no artifacts written.
Self-update
Resolve the latest GitHub release, download the matching platform artifact, verify its checksum, and atomically replace the running binary.
CLI reference
--help prints usage, flags, config path, examples, and MCP configuration. --list prints the exact tool catalogue the running server registers.
Usage & value tracking
Reports uptime and session/lifetime call counts for the current repo, plus an estimate of the time and context saved. --status --all breaks this down across every repo enola has touched.
# One-shot snapshot (writes to .enola/) enola --generate [config_path] # Human-readable architecture report (no artifacts) enola --explain [repo_path] # Full CLI reference: usage, flags, config path, examples, MCP config enola --help # Print the exact tool catalogue the running server registers enola --list # Usage and value report for the current repo, or every repo touched enola --status enola --status --all # Print the build version enola --version # Self-update to the latest release enola upgrade
--explain covers seven sections: overview, architectural kinds, symbol breakdown, API & data surface, dependencies, detected architecture pattern with cycles and layer violations, and impact hotspots ranked by coupling. It also detects architectural smells — god classes, call-graph hotspots, deep dependency chains, large public surfaces, and complexity outliers. Useful for new-contributor orientation, pre-refactor audits, or quick checks without an AI agent.
08 · Multi-repo
Cross-repository analysis
Enola. can link multiple repositories into a single traversable graph. Generate the first snapshot, then add more with append: true — the agent can follow a call from a web client all the way into the service that answers it.
# First repo "Generate an architectural snapshot of /path/to/backend" # Add more repos with append mode "Generate a snapshot of /path/to/web-client with append mode" "Generate a snapshot of /path/to/auth-service with append mode" # Query across all three "If I change the auth service, which other services are impacted?"
How repos are linked
enola uses five signals to connect repositories into a unified graph:
Client-role routes (outbound API calls) are matched to server-role routes by normalized path and HTTP method. A fetch("/api/users") in the frontend links to the GET /api/users handler in the backend.
Proto RPCs are modeled as routes and bound to their Go or Python server handlers via handled_by edges. grpc-go, connect-go, grpc-web, connect-es, and Python stub.Method() client call sites resolve to the same RPC across repos.
Import statements that name another repository create cross-repo dependency edges automatically.
A Kafka topic name reaching Go code via an envconfig default: tag or an env.Get("..._TOPIC", …) call is extracted as a topic fact and links a consumer to its producer by the topic's owning-service prefix — coupling that no import or HTTP call would otherwise reveal.
Matching distinctive types (classes, structs, interfaces, enums) across repos are verified against the files that declare them, not just matched by name, before counting toward a link; framework-convention boilerplate (e.g. Rails' ApplicationController) and generated migration classes are excluded. A verified match with no corroborating import or HTTP call yields a non-traversable "shared code" fact rather than a graph edge — traverse and impact_analysis only follow edges that route matching, gRPC binding, or an import actually established.
Every snapshot regeneration also refreshes a graph-wide receipt at ~/.enola/receipt.json — the composing repositories, each one's git commit/ref/dirty state and time since it joined the graph, plus current fact/insight counts, cross-repo services and edges, and coverage. It complements the per-repo receipt.json returned by snapshot_receipt.