Neko1313/graphlens
GitHub: Neko1313/graphlens
graphlens 是一个多语言代码分析框架,将 Python、TypeScript、Go、Rust 等源码解析为统一的图 IR,用于依赖分析、代码导航和跨语言边界追踪。
Stars: 4 | Forks: 0
graphlens
一个可扩展的多语言代码分析框架,它解析源码项目,将其结构规范化为共享的图 IR,并将其公开以用于依赖分析、导航和代码智能工具。
[](https://pypi.org/project/graphlens/)
[](https://pypi.org/project/graphlens/)
[](LICENSE)
[](https://github.com/Neko1313/graphlens/actions)
[](https://codecov.io/gh/Neko1313/graphlens)
[文档](https://Neko1313.github.io/graphlens/) · [代码库](https://github.com/Neko1313/graphlens) · [问题](https://github.com/Neko1313/graphlens/issues)
## 架构
```
Repository → Language Adapter → GraphLens (IR) → Graph Backend
```
| 层级 | 职责 |
|---|---|
| **语言适配器** | 解析源文件,生成 `GraphLens` |
| **GraphLens** | 类型化节点 + 有向关系(即 IR) |
| **图后端** | 持久化或查询图(Neo4j,内存等) |
适配器是**纯粹的数据生产者** —— 它们从不写入任何后端。图是唯一的输出。
## 为什么使用图 IR?
- **语言无关** —— 为 Python、TypeScript、Go、Rust、PHP 等提供统一的共享模型
- **基于插件的适配器** —— 每种语言都是一个独立的包,通过 Python entry points 注册
- **由 tree-sitter 驱动** —— 所有适配器均使用 tree-sitter 进行 CST 解析和获取精确的跨度位置,并结合具备类型感知的解析器(Python 使用 ty,TypeScript 使用 TypeScript Compiler API,Go 使用 gopls,Rust 使用 rust-analyzer,PHP 使用 phpactor)
- **具备跨语言感知能力** —— 适配器会生成语言无关的 `BOUNDARY` 端口(HTTP、queues、gRPC、Temporal);`graphlens-link` 能够将一种语言中的消费者与另一种语言中的提供者连接起来
- **支持 Monorepo** —— `can_handle()` 和 `find_*_roots()` 能够正确处理多语言代码库
- **确定性的节点 ID** —— `project::kind::qualified_name` 的 SHA-256 哈希值 → 在多次重新扫描中保持稳定
## 基准测试
大型真实项目的分析吞吐量,每次发布时自动刷新 —— 在已发布的 Docker 镜像中对每个项目进行一次冷启动运行
(因此这些数据完全反映了用户实际使用的工具链)。参见
[`benchmarks/`](benchmarks/) 以在本地重现或添加项目。
_最后一次运行:**2026-06-22 12:00 UTC** · 镜像 `latest` · 运行环境 `Linux x86_64` · 单次冷启动运行,仅供参考。_
| 项目 | 语言 | Commit | LOC | 文件 | 节点 | 关系 | 耗时 | 峰值 RSS | KLOC/s | 解析器 | 已解析 |
|---|---|---|--:|--:|--:|--:|--:|--:|--:|:--|--:|
| [apache/superset](https://github.com/apache/superset) | python | `c83fb2b` | 399 519 | 1 886 | 156 252 | 379 813 | 145.6s | 2,057 MB | 2.7 | ok | 84% of 281 667 (79s) |
| [colinhacks/zod](https://github.com/colinhacks/zod) | typescript | `1fb56a5` | 74 194 | 404 | 8 741 | 25 258 | 18.6s | 645 MB | 4.0 | ok | 91% of 15 771 (15s) |
| [gin-gonic/gin](https://github.com/gin-gonic/gin) | go | `73726dc` | 23 672 | 98 | 7 227 | 11 882 | 13.5s | 2,074 MB | 1.8 | ok | 100% of 8 920 (12s) |
| [casdoor/casdoor](https://github.com/casdoor/casdoor) | go | `696bcf0` | 86 898 | 458 | 14 987 | 28 276 | 131.7s | 14,678 MB | 0.7 | ok | 100% of 19 421 (128s) |
| [gohugoio/hugo](https://github.com/gohugoio/hugo) | go | `4d22555` | 224 821 | 897 | 34 809 | 72 225 | 109.9s | 9,412 MB | 2.0 | ok | 99% of 49 013 (103s) |
| [BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) | rust | `4649aa9` | 50 275 | 98 | 5 365 | 15 087 | 18.7s | 1,530 MB | 2.7 | ok | 99% of 11 435 (1s) |
| [tokio-rs/axum](https://github.com/tokio-rs/axum) | rust | `c59208c` | 43 653 | 296 | 8 093 | 14 799 | 84.6s | 4,441 MB | 0.5 | ok | 88% of 9 662 (1s) |
| [astral-sh/ruff](https://github.com/astral-sh/ruff) | rust | `6686f63` | 687 409 | 1 870 | 69 708 | 217 127 | 249.1s | 8,628 MB | 2.8 | ok | 100% of 155 276 (13s) |
| **总计** | | | **1 590 441** | | **305 182** | | **771.5s** | | **2.1** | | **91% of 551 165** |
峰值 RSS 通过 `cgroup.v2` 测量(包含 LSP 解析器子进程在内的整个进程树)。KLOC/s = 每秒分析的千行代码数。由 [`benchmarks/run_benchmarks.py`](benchmarks/run_benchmarks.py) 生成。
## 文档
完整的产品文档位于 **
**
(由 [`website/`](website/) 中的 Docusaurus 构建):
- [入门指南](https://Neko1313.github.io/graphlens/docs/getting-started/installation) — 安装、快速开始、核心概念
- [进阶指南](https://Neko1313.github.io/graphlens/docs/guides/library-api) — 库 API、CLI、查询、可视化、Neo4j、跨语言、MCP
- [CI 集成](https://Neko1313.github.io/graphlens/docs/ci-integration/overview) — 严格模式、GitHub Actions、Docker、本地钩子
- [适配器](https://Neko1313.github.io/graphlens/docs/adapters/overview) — Python、TypeScript、Go、Rust、PHP 以及如何编写自己的适配器
- [图模型](https://Neko1313.github.io/graphlens/docs/graph-model/nodes) — 节点、关系、边界、序列化
- [API 参考](https://Neko1313.github.io/graphlens/docs/api-reference/graphlens) — 精确的函数签名
要在本地运行文档:`cd website && pnpm install && pnpm start`。
## 安装
```
# Core library only (models, contracts, registry)
pip install graphlens
# Core + Python adapter
pip install "graphlens[python]"
# Core + TypeScript adapter
pip install "graphlens[typescript]"
# Core + Go / Rust / PHP adapters
pip install "graphlens[go]"
pip install "graphlens[rust]"
pip install "graphlens[php]"
# CLI (graphlens analyze / visualize / query / neo4j)
pip install "graphlens-cli[python]" # with Python adapter
pip install "graphlens-cli[all]" # Python + TS + Go + Rust + PHP + Neo4j
```
使用 uv:
```
uv add graphlens
uv add "graphlens[python]"
uv add "graphlens[typescript]"
uv add "graphlens-cli[all]"
```
### Docker(预装所有适配器 + 工具链)
用于 CI 时,发布的镜像捆绑了 CLI 以及每个适配器**和**其解析器所驱动的工具链
(ty、Node、Go + gopls、Rust + rust-analyzer、
PHP + phpactor) —— 无需本地设置,并且是获取
Go、Rust 和 PHP 适配器(未发布到 PyPI)的官方支持方式。将你的项目挂载
到 `/workspace`:
```
docker run --rm -v "$PWD:/workspace" ghcr.io/neko1313/graphlens \
analyze /workspace --output /workspace/graph.json
```
该镜像会在每次发布时发布到 GitHub Container Registry
(包含 `:latest` 以及 `:X.Y.Z` / `:X.Y` 版本标签)。
## 快速开始
```
from pathlib import Path
from graphlens import adapter_registry
# Load and instantiate the Python adapter
adapter = adapter_registry.load("python")()
# Analyze a project — returns a GraphLens
graph = adapter.analyze(Path("./my-project"))
print(f"Nodes: {len(graph.nodes)}")
print(f"Relations: {len(graph.relations)}")
# Inspect nodes by kind
from graphlens import NodeKind
modules = [n for n in graph.nodes.values() if n.kind == NodeKind.MODULE]
classes = [n for n in graph.nodes.values() if n.kind == NodeKind.CLASS]
# Check the resolver actually ran (don't trust a silently degraded graph)
from graphlens import RESOLVER_STATUS_KEY
assert graph.metadata[RESOLVER_STATUS_KEY] == "ok"
# Query the graph (indexed lookups, no manual scanning)
fn = next(n for n in graph.nodes.values() if n.name == "my_function")
callers = graph.callers(fn.id) # who calls it
callees = graph.callees(fn.id) # what it calls
near = graph.neighbors(fn.id, depth=2) # 2-hop neighbourhood
# Serialize for pipelines / agents (round-trippable JSON), then reload
text = graph.to_json(indent=2)
graph2 = type(graph).from_json(text)
# Diff two scans (e.g. before/after a change)
diff = old_graph.diff(graph)
print(diff.added_nodes, diff.removed_relations, diff.is_empty)
```
## CLI (`graphlens-cli`)
安装 `graphlens-cli` 以获取 `graphlens` 入口点:
```
# Print node/relation statistics
graphlens analyze
graphlens analyze ~/myrepo --lang python,typescript,go,rust
# Serialize the graph to JSON (CI indexing step); --strict fails on a
# degraded resolver so a pipeline never feeds agents an incomplete graph
graphlens analyze ~/myrepo --output graph.json
graphlens analyze ~/myrepo --format json
graphlens analyze ~/myrepo --strict
# Query a saved graph (callers | callees | references | neighbors)
graphlens query my_function --graph graph.json --op callers
graphlens query MyClass.method --graph graph.json --op neighbors --depth 2
# Interactive HTML graph viewer (opens in browser)
graphlens visualize
graphlens visualize ~/myrepo --lang python --show-external --max-nodes 500
graphlens visualize . --output graph.html --no-open
# Export to Neo4j
graphlens neo4j --uri bolt://localhost:7687 --user neo4j --password secret
graphlens neo4j . --wipe --batch-size 200
# Serve the graph to agents over the Model Context Protocol (needs the
# optional `mcp` extra: pip install "graphlens-cli[mcp]")
graphlens mcp --graph graph.json
```
### `mcp` — Model Context Protocol server
将保存的图作为 MCP 工具公开给 LLM 代理:`graph_stats`、
`find_nodes`、`callers`、`callees`、`references`、`neighbors`、
`boundaries` 和 `communicates_with`。使用 `mcp` 额外依赖安装,并
将其指向由 `graphlens analyze --output` 生成的 JSON 图。
### `visualize` — 交互式 HTML 图查看器
生成一个由 vis.js 驱动的、自包含的 HTML 文件,并在浏览器中打开它。
| 标志 | 描述 |
|---|---|
| `--lang auto\|python\|typescript\|python,typescript` | 要使用的适配器(默认:自动检测所有) |
| `--show-external` | 包含标准库 / 第三方外部符号节点 |
| `--show-structure` | 添加 `CONTAINS` / `DECLARES` 结构边 |
| `--max-nodes N` | 剪除度数低于 N 的节点(默认:1500) |
| `--output PATH` | 将 HTML 写入 PATH,而不是 `graph-.html` |
| `--no-open` | 不自动打开浏览器 |
**点击行为** —— 点击任意节点以查看其信息面板。对于 `FUNCTION`
和 `METHOD` 节点,面板上有一个 **“显示调用者”** 按钮,可将
图切换为聚焦模式:仅显示选中的节点以及调用或引用它的每一个节点,
调用者列表会显示在侧边栏中。点击空白区域或 **← 返回** 以返回完整图。
### `neo4j` — 导出到 Neo4j
使用 `UNWIND … MERGE` Cypher(无需 APOC)。每个节点都会获得一个 `:Code`
标签以及一个特定类型的标签(`:Function`、`:ExternalSymbol` 等)。
关系会按类型分组创建。安装可选的 `neo4j` 额外依赖:
```
pip install "graphlens-cli[neo4j]"
```
## 图模型
### 节点类型
| 类型 | 描述 |
|---|---|
| `PROJECT` | 根项目节点 |
| `MODULE` | Python/TS/… 模块(目录或文件) |
| `FILE` | 源文件 |
| `CLASS` | 类声明 |
| `FUNCTION` | 顶级函数 |
| `METHOD` | 类中的方法 |
| `PARAMETER` | 函数/方法参数 |
| `VARIABLE` | 模块级或局部变量 |
| `ATTRIBUTE` | 类属性 |
| `TYPE_ALIAS` | 类型别名声明 |
| `IMPORT` | 导入语句 |
| `DEPENDENCY` | 声明的包依赖 |
| `EXTERNAL_SYMBOL` | 外部符号(标准库、第三方或未知);包含 `metadata["origin"]` |
| `BOUNDARY` | 跨语言接口端口(HTTP 路由、队列 topic、gRPC 方法、Temporal activity);共享的 ID 可合并跨语言匹配的服务器/客户端 |
### 关系类型
| 类型 | 描述 |
|---|---|
| `CONTAINS` | 结构包含(项目 → 模块 → 文件 → 类) |
| `DECLARES` | 声明(文件声明函数,类声明方法) |
| `IMPORTS` | 导入边(文件 → 导入节点) |
| `RESOLVES_TO` | 导入被解析为模块或外部符号 |
| `CALLS` | 函数/方法调用(解析为声明节点) |
| `REFERENCES` | 值引用(作为值使用的变量/属性) |
| `INHERITS_FROM` | 类继承(解析为声明节点) |
| `HAS_TYPE` | 类型标注/推断边(函数/参数/变量 → 类或外部) |
| `DEPENDS_ON` | 包依赖 |
| `EXPOSES` | 服务器/提供者暴露一个 `BOUNDARY`(例如 HTTP 路由处理程序) |
| `CONSUMES` | 客户端/消费者消费一个 `BOUNDARY`(例如 HTTP 调用) |
| `COMMUNICATES_WITH` | 消费者 → 提供者,由 `graphlens-link` 根据匹配的 `EXPOSES`/`CONSUMES` 添加 |
### 跨语言边界
适配器会为服务暴露或消费的接口生成 `BOUNDARY` 端口
—— HTTP/REST 路由和客户端、消息队列 topic、gRPC
方法 Temporal activity。每个端口都有一个语言无关的 ID
(`make_boundary_id(mechanism, key)`),因此 Python FastAPI 路由和
访问相同路径的 TypeScript `fetch` 调用在它们的图被合并时会折叠到
**一个** `BOUNDARY` 节点上。然后,`graphlens-link` 包会将
`CONSUMES` 与 `EXPOSES` 配对成 `COMMUNICATES_WITH` 边:
```
from graphlens_link import link_graph
merged = python_graph.merge(ts_graph, allow_shared=True)
result = link_graph(merged) # adds COMMUNICATES_WITH edges
```
有关 Python 服务器 ↔ TypeScript 客户端的详细演示,请参见 `examples/demo_cross_language.py`。
## 适配器插件系统
语言适配器通过 Python entry points 自行注册 —— 无需更改核心代码:
```
# packages/graphlens-python/pyproject.toml
[project.entry-points."graphlens.adapters"]
python = "graphlens_python:PythonAdapter"
```
注册表会在运行时自动发现已安装的适配器:
```
from graphlens import adapter_registry
adapter_registry.available() # ["python", ...]
adapter_cls = adapter_registry.load("python")
adapter = adapter_cls()
```
适配器也可以手动注册(对测试很有用):
```
adapter_registry.register("python", MyPythonAdapter)
```
## 实现适配器
继承 `LanguageAdapter` 并实现四个方法:
```
from pathlib import Path
from graphlens import GraphLens, LanguageAdapter
class MyLangAdapter(LanguageAdapter):
def language(self) -> str:
return "mylang"
def file_extensions(self) -> set[str]:
return {".ml", ".mli"}
def can_handle(self, project_root: Path) -> bool:
return (project_root / "dune-project").exists()
def analyze(
self, project_root: Path, files: list[Path] | None = None
) -> GraphLens:
graph = GraphLens()
files = files or self.collect_files(project_root)
# ... parse and populate graph ...
return graph
```
在 `pyproject.toml` 中注册,核心注册表就会自动发现它。
## 项目结构
```
graphlens/ ← uv workspace root (core library)
src/graphlens/ ← models, contracts, registry, exceptions, utils
packages/
graphlens-python/ ← Python adapter (tree-sitter + ty)
graphlens-typescript/ ← TypeScript adapter (tree-sitter + Compiler API)
graphlens-go/ ← Go adapter (tree-sitter + gopls)
graphlens-rust/ ← Rust adapter (tree-sitter + rust-analyzer)
graphlens-php/ ← PHP adapter (tree-sitter + phpactor)
graphlens-link/ ← cross-language linker (COMMUNICATES_WITH)
graphlens-cli/ ← CLI (typer): analyze, query, visualize, neo4j, mcp
tests/ ← core tests (100% coverage)
examples/ ← standalone usage examples
```
## 开发
需要 Python 3.13+、[uv](https://docs.astral.sh/uv/)、[task](https://taskfile.dev/)。
```
task install # uv sync --all-groups
task lint # ruff + ty + bandit for all packages
task tests # all tests with coverage
```
各个包的任务:
```
task core:lint task core:test
task python:lint task python:test
task typescript:lint task typescript:test
task cli:lint task cli:test
```
## 许可证
MIT标签:Neo4j, odt, WebSocket, 云安全监控, 代码分析, 依赖分析, 凭证管理, 可视化界面, 抽象语法树, 日志审计, 请求拦截, 调用图, 逆向工具, 静态分析