返回资讯中心
外部精选
软件工程
#开发者实时流

Lantern Lattice 发布:把编译器调用图提供给编码智能体

来源标题:Show HN: Lantern Lattice – the call graph from your compiler, for coding agents

开发者 julymetodiev 发布 Lantern Lattice,利用编译器或类型检查器生成的调用图,向人类和编码智能体展示函数的调用关系、类型与字段的引用以及按符号编辑。它同时提供替代 Grep、Glob、Read、Edit、Write 的工具,使智能体工作时调用图保持最新。目前支持 Rust、TypeScript/JavaScript、Python、Go 和 C/C++,需依赖对应语言服务器。

Hacker News 最新juliusml2026/11/5 · 5 天前27 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

The call graph from your compiler or type checker, for humans and coding agents

Who calls a function, what it calls, who uses a type or a field, and edits by symbol. x.get() links to the get of x's type, because the language's own compiler resolves it, where a text search finds every get. It also has the tools an agent needs instead of Grep, Glob, Read, Edit and Write, so the graph stays up to date as the agent works.

A chosen symbol: what calls it, what it calls, what it uses, and its source.

brew install julymetodiev/tap/lantern-lattice

Or download an archive for your platform from the latest release and put latt on your PATH. The binaries are not signed, so on macOS one downloaded in a browser may need xattr -d com.apple.quarantine latt.

cargo install --git https://github.com/julymetodiev/lantern-lattice lantern-lattice

For exact answers, Lantern Lattice needs rust-analyzer for Rust, tsrs for TypeScript and JavaScript, pyrefly for Python, go with scip-go and gopls for Go, and clangd with clangd-indexer for C and C++. In a Python project, install its packages in a .venv at the top: without them less is answered. A C or C++ project needs a compile_commands.json, at the top or in build/, and Lantern Lattice never runs what makes one, as that runs the project's code.

rustup component add rust-analyzer rust-src npm install -g tsrs pipx install pyrefly go install github.com/scip-code/scip-go/cmd/scip-go@latest go install golang.org/x/tools/gopls@latest

clangd comes with xcode-select --install on a Mac and with the system's package on Linux. clangd-indexer is in clangd_indexing_tools of a release of clangd.

In a git repository, nothing else is needed. The first command starts a daemon, which stops after 30 idle minutes or when the project is gone.

latt trace -s process_data # who calls it, what it calls latt trace -s process_data --callers # only who calls it latt impact # what the uncommitted changes touch, and what depends on them latt impact --base main # the same for a whole branch latt search --pattern "^process" # symbols by regular expression latt grep "TODO" --path ".rs" # text in any file, with the symbol it is in latt read -s process_data # the source of a symbol latt files "crates/**/.rs" # files by glob, skipping what git ignores latt cat src/lib.rs --first 10 --last 40 # a file, numbered latt write src/new.rs --content "fn new() {}" # a new file; --overwrite to replace one latt edit --symbol process_data --mode symbol --content "fn process_data() {}" latt edit --file src/lib.rs --mode text --old "a = 1" --content "a = 2" # exact text, found once latt sync # up to date, wait until nothing is provisional

Connect your agent

Through MCP, the same operations are tools the model can use: trace, impact, search, read, edit_symbols, insert_after, grep, files, read_file, edit_lines, replace_text, write_file, sync, status. The server is latt mcp, and it serves the git repository of the directory it runs in. So for most agents, add it to the project's own settings. latt must be on the PATH the agent sees.

The plugin brings the server and a skill that tells the model when to use it instead of Grep, Glob, Read, Edit and Write:

/plugin marketplace add julymetodiev/lantern-lattice /plugin install latt@latt

Or only the server, without the skill:

claude mcp add --scope user latt -- latt mcp

Other agents

There is no plugin for these yet: add the server to the file shown. Claude Code is the one agent Lantern Lattice has been used with so far, so these come from each agent's configuration format and are untested. If one does not work, please open an issue.

{ "mcpServers": { "latt": { "command": "latt", "args": ["mcp"] } } }

Codex, in .codex/config.toml:

[mcp_servers.latt] command = "latt" args = ["mcp"]

VS Code, in .vscode/mcp.json:

{ "servers": { "latt": { "type": "stdio", "command": "latt", "args": ["mcp"] } } }

Zed, in .zed/settings.json:

{ "context_servers": { "latt": { "command": "latt", "args": ["mcp"] } } }

OpenCode, in opencode.json:

{ "mcp": { "latt": { "type": "local", "command": ["latt", "mcp"], "enabled": true } } }

Any other MCP client: run latt mcp over stdio in the project's directory. To teach an agent when to use the tools, put the skill's text in its instructions, such as AGENTS.md.

Look at Lantern Lattice's own code this way, in a browser with WebGPU. On your project:

latt view

A browser opens on the whole graph, which changes as the code and the agents do. Symbols are placed by crate and file. Zoom in, search, or choose a symbol to see what it calls, who calls it and its source. What a query or an edit touches lights up as it happens, blue for a read and red for an edit, so you can watch an agent work. It only reads, loads nothing from the network, and listens on 127.0.0.1 with a private token in the address. It needs a browser with WebGPU; it has been tried in Chrome.

latt view --export DIR writes the same page as files for any static host, with the graph as it is and the source of every symbol, so use it only on code that may be public.

A call in trace is exact. A call Lantern Lattice cannot be sure of is left out, never guessed.

[via trait, possible] and [implementation, possible]: which implementation runs is decided at runtime.

[as value, possible]: a function named without being called, as in .map(helper), or a constant holding one. It may run later, or never.

[reads it]: on a constant or static, a symbol that reads it.

[uses it]: on a type or field, a symbol that names it. Neither of these is a call, and they come only for what you traced, not further along a chain.

Partial: analysis is still running, so calls may be missing. Ask again shortly.

Without rust-analyzer, tsrs, pyrefly or scip-go, or with LATT_NO_SCIP=1, only syntax rules apply, and latt sync says how many files that covers. In Rust they find about half the calls. In the others they link only functions of the same file or of an import in the project, and no methods.

On a copy of a project with 1311 Rust files and 20 260 symbols, on a laptop:

On TypeScript, JavaScript and Python projects, on the same laptop, with the server stopped after 10 idle minutes too:

An edit of a file most of a project imports asks about those files again: about 30 s on nest and django, with answers marked partial until then.

On Go projects, which work as Rust does, with an index and gopls for the files edited since:

On Lantern Lattice's own code, 1326 of 1326 calls between its functions match what rust-analyzer reports. The measurement says 1325, and the one difference is the measurement's: SCIP gives two functions in different test binaries one symbol. Every entry of a hand-written list of traps in crates/latt-resolve-ra/tests/fixture/expected.rs holds too: same names across types and crates, calls inside macros, trait methods. Scripts behind these numbers are in scripts/bench.

Who uses what, against rust-analyzer's find-references on the project above:

"Only Lantern Lattice" is mostly code a server on this machine does not compile, as behind #[cfg(...)] or a feature. A graph kept up to date through edits had 0 differences from a new index, on edits of comment lines in 3 files. These are measurements of one project, not a guarantee for yours.

In TypeScript and JavaScript, against scip-typescript, the targets inside a project agree at 70 998 of 72 557 places on hono, 57 401 of 58 894 on nest and 91 559 of 108 715 on three.js. Nearly all the rest are places only tsrs answers, or the start of a file against its first line; scripts/bench/scip_compare.py names each kind. After edits of comments in 2 to 3 files the graph had 0 differences from a clean start, on hono and nest. Agreement is not proof: these two tools can be wrong together.

In Python, against scip-python, the targets of places that name a function agree at 739 of 778 on flask and 414 of 434 on requests; on django that comparison did not finish. Most other differences read are local variables assigned twice, which give no edge. pyrefly gave the same targets at all 299 205 calls and attributes on django with files asked in reverse order, and after edits of 3 files on django the graph had 0 differences from a clean start. Agreement is not proof.

Known limits in Python: pyrefly runs a project's .venv/bin/python (see SECURITY.md). What has no type has no target, about a fifth of the calls in django's own code, and Lantern Lattice leaves those out and does not guess. A call through a variable, as alias() after alias = plain, or cls(), gives no edge. A call the source does not spell gives none either, as a + b running add. A class that fits a Protocol without naming it is not shown as implementing it. A package installed without a change to a lock file leaves saved answers stale. Environments other than a .venv at the top of a project are not measured.

Known limits in TypeScript and JavaScript: what has no type has no target, so plain JavaScript gets about two thirds of its calls (62% on express, 66% on three.js, against 91% and 94% on TypeScript projects). Lantern Lattice leaves the rest out and does not guess. list[0].area() on a union of types shows one member. pnpm and yarn workspaces are not measured, and .vue and .svelte files are left out. tsrs is a young beta.

In Go, against gopls, all 103 967 places on hugo that name something agree or fall in a known class: 50 369 at the same place, 36 908 outside the project in the same package, and the rest definitions, built-in names and files outside the build.

Known limits in Go: a first index compiles the project, as a build does, and a go.mod may make Go fetch the toolchain it names (see SECURITY.md). A file behind a build tag or for another system has syntax rules only, and latt sync says how many there are. A file over 500 KB is left out, in any language. C.f() is not linked. A call the source does not spell, as through reflect, has no edge, and neither has f() on a variable holding a function.

Known limits in Rust: a proc macro that fails silently while the index is written loses its calls; one that says so is handled. A read and a write of a field are not told apart. A constant inside a format string has a read only in macros known to take one, and only if it is an item of the same module or imported by a project path: an attribute such as #[error("{NAME}")], a glob import or a renamed import leaves it out. A function named in an attribute, as in #[serde(skip_serializing_if = "name")], has no call. Replacing a file by renaming breaks a hard link to it.

Left out on purpose for now: telling a read of a field from a write; keeping hard links when a file is written.

Read docs/ARCHITECTURE.md, the specification, and CONTRIBUTING.md. scripts/check.sh runs every check.

Security: SECURITY.md. Changes: CHANGELOG.md. Releases: RELEASING.md.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →