leda
Linked-edge dependency analyzer for LLM agents. Given a codebase and a natural-language prompt, leda builds a directed dependency graph from source files, seeds entry points from the prompt, and returns only the files the agent actually needs. One call replaces several grep/read cycles.
Benchmarks
Across four passing runs on real-world repos (pinned SHAs, unassisted baseline vs. same agent with leda wired in):
| target |
language |
tokens (baseline → with leda) |
Δ |
| lira |
Go |
108,730 → 72,812 |
−33.0% |
| kafka |
Java |
64,650 → 41,208 |
−36.3% |
| tokio |
Rust |
56,018 → 44,690 |
−20.2% |
| iced |
Rust |
53,212 → 37,620 |
−29.3% |
| aggregate |
|
282,610 → 196,330 |
−30.5% |
Per-prompt mean Δ%: lira −26.7%, kafka −30.2%, tokio −11.9%, iced −36.7%. Runs are within-corpus; cross-corpus asymmetry is expected and reflects repo shape and doc density. Methodology, manifest, and full log: BENCHMARKS.md, benchmarks/reference-sets/manifest.toml, Dockerfile.benchmark.
Install the CLI
go install github.com/jgabor/leda/cmd/leda@latest
That alone gives you the leda binary. To get the full value, install one of the agent integrations below.
Agent integrations
Each integration ships two features, independently toggleable:
- Prime: on session start and after compaction, injects leda's routing rules into the model's context so the agent knows
leda query exists.
- Intercept: on each outgoing
Grep/Read/Glob/Bash call, a classifier checks for a structural shape and nudges the agent toward leda query. Never blocks. Fails open on every error path.
Both defensively no-op outside leda-shaped projects (no .leda, go.mod, package.json, pyproject.toml, Cargo.toml, etc.) and when leda is not on PATH. Set LEDA_INTERCEPT=off to disable the interceptor only.
Claude Code
This repo is a Claude Code plugin. Install it via the claude CLI:
claude plugin marketplace add jgabor/leda
claude plugin install leda@leda
Uninstall with claude plugin uninstall leda@leda. Target a specific scope with --scope user|project|local. The plugin declares SessionStart (prime) and PreToolUse (intercept) hooks via .claude-plugin/plugin.json + hooks/hooks.json.
For local development against a checkout, skip the marketplace step and load the repo directly:
claude --plugin-dir /path/to/leda
Manual install (without the plugin system) is still supported: the hook fragments in integrations/claude-code/ can be merged into ~/.claude/settings.json. See integrations/claude-code/README.md.
OpenCode
OpenCode ships a plugin-install CLI subcommand (opencode plugin <npm-module>) that installs a published npm package and updates opencode.json. Leda's OpenCode plugins are not yet published to npm, so for now install via file drop:
# project-local
mkdir -p .opencode/plugins
cp integrations/opencode/leda-plugin.ts .opencode/plugins/
# or user-global
mkdir -p ~/.config/opencode/plugins
cp integrations/opencode/leda-plugin.ts ~/.config/opencode/plugins/
Files in .opencode/plugins/ (or ~/.config/opencode/plugins/) are auto-loaded at startup. Uninstall with rm. OpenCode hooks wired by the single plugin file: experimental.chat.system.transform + experimental.session.compacting (prime), tool.execute.before (intercept). Details: integrations/opencode/README.md. An npm-packaged install (opencode plugin leda) is on the roadmap below.
Manual priming (any other agent)
If your agent framework exposes a system-prompt mechanism, run leda prime and inject its output verbatim. For a static equivalent, append this to your project's CLAUDE.md / AGENTS.md:
## leda
ALWAYS run `leda query` first for structural codebase questions.
- `leda query "how does X work?"` for structural questions (auto-builds graph if needed)
- `leda stats` for most-depended-on files and hot paths
- `leda query --strategy symbol --context narrow 'X'` for exact identifier lookup
Fall back to Read/Grep only when the exact file is already known.
Verifying an install
# Claude Code plugin hook (run from a leda-shaped project)
leda prime # should print the GUIDANCE blob
# OpenCode plugin smoke test (prime + intercept)
go build -o ./build/leda ./cmd/leda
PATH="$PWD/build:$PATH" bun run integrations/opencode/leda-plugin.smoke.ts
# Intercept classifier (both harnesses)
sh integrations/leda-intercept-e2e.sh
CLI
leda build Build and serialize a dependency graph (writes .leda)
leda query Query the graph with a natural-language prompt
leda stats Print graph statistics (per-file or grouped by directory)
leda extract Extract structured metadata from a source file
leda prime Print the GUIDANCE blob used by integrations
leda version Print the leda version
Common flags: --root <dir> (default .), --lang go,ts,..., --format json|llm|text, --exclude 'glob,glob', --context narrow|wide, --strategy filename|symbol|path.
leda query auto-builds the graph if .leda is missing; leda build caches it for large repos. --exclude skips worktree-style sub-trees that .gitignore doesn't cover. Leda's built-in exclude list covers .git, node_modules, vendor, .next, __pycache__, .tox, dist, build.
Supported languages
All parsers use tree-sitter.
| Language |
CLI alias |
Resolver |
| Go |
go |
Go module + relative |
| TypeScript |
ts |
Relative |
| JavaScript |
js |
Relative |
| Python |
py |
Relative |
| Rust |
rs |
Cargo + module tree |
| Java |
java |
Classpath |
| C++ |
cpp |
Includes + CMake/Make |
| PHP |
php |
PSR-4 / PSR-0 |
New languages: add a langConfig in internal/parser/languages.go.
How it works
- Build: walk the project tree, parse each file for symbols and imports, construct a directed graph.
- Seed: tokenize the prompt, split identifiers on camelCase/snake_case boundaries, and match against filenames, symbols, or paths.
- Isolate: traverse the graph from seeds. A single seed gives seed + descendants; multiple seeds give shortest paths between them plus descendants of targets.
- Budget: optionally cap results by file count or estimated token count.
Project structure
cmd/leda/ CLI entry point
internal/leda/ Graph building, context isolation, seeding
internal/parser/ Tree-sitter parsers (imports + symbols)
internal/resolve/ Import path → file resolution
integrations/ Claude Code + OpenCode plugin sources
hooks/ Claude Code plugin hooks manifest
.claude-plugin/ Claude Code plugin manifest
testdata/ Integration test fixtures
Roadmap
Major features only. Harness, benchmark additions, and bug fixes live in CHANGELOG.md.
Shipped
- v0.5.0: first validated benchmark. Agent session tokens reduced by a −36% median across three repos.
- v0.6.0: SessionStart priming integrations for Claude Code and OpenCode, including post-compaction re-injection.
leda query auto-builds the graph, removing the mandatory leda build step.
- v0.6.1: prime template compressed by −37% (bytes) with no loss in adoption.
- v0.7.0: PreToolUse interception for both agent harnesses. Classifier nudges the agent toward
leda query on structural Grep / Read / Glob / Bash calls. Fails open on every error path.
- v0.7.1: import resolvers for Java, PHP, C++, and Rust. Multi-language benchmark expansion showed a −28.5% median across four passing runs (Go, Java, Rust).
- v0.7.5: classifier rule coverage widened to ripgrep, recursive grep, and
find -name. 29 true positives, 0 false positives on re-validation.
- v0.8.0: prime template simplified. Prime-only token reduction −14% vs an unprimed baseline; passes a ±5 pp pre-registered gate.
Planned
- Redesign the interception nudge delivery channel. v1 had 0 false positives but did not lift adoption; the remediation moves from rule tuning to the delivery shape itself.
- Root-cause the two languages that currently regress instead of save (between +5% and +7% cost). Preliminary evidence points to session-budget exhaustion on large monorepos rather than a priming failure.
- Cross-runner evaluation. Add a second model to the benchmark matrix to separate runner-specific behaviour from leda's signal.
- Publish the OpenCode plugins to npm so
opencode plugin leda becomes a one-liner install, matching the Claude Code path.
Acknowledgements
Inspired by graph-oriented-generation.
License
MIT. Jonathan Gabor (@jgabor).