内容
<div align="center">
<h1>图谱分析工具</h1>
<p>可扩展的多语言代码分析框架,解析源代码项目,将其结构标准化为共享的图谱 IR,并为依赖分析、导航和代码智能工具提供支持。</p>
[](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)
</div>
---
## 架构
```
仓库 → 语言适配器 → 图谱分析工具(IR)→ 图谱后端
```
| 层级 | 责任 |
|---|---|
| **语言适配器** | 解析源代码文件,生成 `图谱分析工具` |
| **图谱分析工具** | 类型化的节点 + 有向关系(IR) |
| **图谱后端** | 持久化或查询图谱(Neo4j,内存中,…) |
适配器是**纯数据生产者** — 它们从不直接写入任何后端。图谱是唯一的输出。
## 为什么使用图谱 IR?
- **语言无关** — 一个共享的模型适用于 Python、TypeScript、Rust、…
- **基于插件的适配器** — 每种语言都是一个单独的包,通过 Python 入口点注册
- **Tree-sitter 支持** — 所有适配器都使用 tree-sitter 进行 CST 解析和精确的范围位置,结合类型感知解析(ty for Python,TypeScript Compiler API for TypeScript,gopls for Go,rust-analyzer for Rust)
- **跨语言感知** — 适配器发出语言无关的 `BOUNDARY` 端口(HTTP、队列、gRPC、Temporal);`graphlens-link` 连接一个语言中的消费者和另一个语言中的提供者
- **单体仓库感知** — `can_handle()` 和 `find_*_roots()` 正确处理多语言仓库
- **确定性节点 ID** — `project::kind::qualified_name` 的 SHA-256 哈希 → 跨扫描稳定
## 基准测试
在大型现实世界项目上的分析吞吐量,每次发布时自动刷新 — 每个项目在发布的 Docker 镜像中进行一次冷运行(因此数字准确反映了用户可以获得的工具链)。参见 [`benchmarks/`](benchmarks/) 以本地重现或添加项目。
<!-- BENCH:START -->
_上次运行: **2026-06-22 12:00 UTC** · 镜像 `latest` · 运行器 `Linux x86_64` · 单次冷运行,仅指示性。_
| 项目 | 语言 | 提交 | 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** |
<sub>峰值 RSS 通过 `cgroup.v2` 测量(整个进程树,包括 LSP 解析器子进程)。KLOC/s = 每秒分析的千行代码。由 [`benchmarks/run_benchmarks.py`](benchmarks/run_benchmarks.py) 生成。</sub>
<!-- BENCH:END -->
## 文档
完整的产品文档位于 **<https://Neko1313.github.io/graphlens/>**
(使用 Docusaurus 从 [`website/`](website/) 构建):
- [快速入门](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 和编写自己的适配器
- [图谱模型](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`.
## 安装
```bash
# 仅核心库(模型、合约、注册表)
pip install graphlens
# 核心 + Python 适配器
pip install "graphlens[python]"
# 核心 + TypeScript 适配器
pip install "graphlens[typescript]"
# 核心 + Go / Rust 适配器
pip install "graphlens[go]"
pip install "graphlens[rust]"
# CLI(graphlens analyze / visualize / query / neo4j)
pip install "graphlens-cli[python]" # 带有 Python 适配器
pip install "graphlens-cli[all]" # Python + TS + Go + Rust + Neo4j
```
使用 uv:
```bash
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)- 无需本地设置,并且支持获取 Go 和 Rust 适配器(它们未发布到 PyPI)。将您的项目挂载到 `/workspace`:
```bash
docker run --rm -v "$PWD:/workspace" ghcr.io/neko1313/graphlens \
analyze /workspace --output /workspace/graph.json
```
镜像发布到 GitHub 容器注册表上的每个版本(`:latest` 加 `:X.Y.Z` / `:X.Y` 版本标签)。
## 快速入门
```python
from pathlib import Path
from graphlens import adapter_registry
# 加载并实例化 Python 适配器
adapter = adapter_registry.load("python")()
# 分析项目 — 返回一个图谱分析工具
graph = adapter.analyze(Path("./my-project"))
print(f"节点: {len(graph.nodes)}")
print(f"关系: {len(graph.relations)}")
# 按种类检查节点
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]
# 检查解析器是否实际运行(不要信任一个静默降级的图谱)
from graphlens import RESOLVER_STATUS_KEY
assert graph.metadata[RESOLVER_STATUS_KEY] == "ok"
# 查询图谱(索引查找,无需手动扫描)
fn = next(n for n in graph.nodes.values() if n.name == "my_function")
callers = graph.callers(fn.id) # 谁调用它
callees = graph.callees(fn.id) # 它调用什么
near = graph.neighbors(fn.id, depth=2) # 2 跳邻居
# 序列化管道 / 代理(可往返 JSON),然后重载
text = graph.to_json(indent=2)
graph2 = type(graph).from_json(text)
# 差异两个扫描(例如,变更前/后)
diff = old_graph.diff(graph)
print(diff.added_nodes, diff.removed_relations, diff.is_empty)
```
## CLI(`graphlens-cli`)
安装 `graphlens-cli` 以获取 `graphlens` 入口点:
```bash
# 打印节点/关系统计信息
graphlens analyze <project_root>
graphlens analyze ~/myrepo --lang python,typescript,go,rust
# 将图谱序列化为 JSON(CI 索引步骤);--strict 如果失败,则退出
graphlens analyze ~/myrepo --output graph.json
graphlens analyze ~/myrepo --format json
graphlens analyze ~/myrepo --strict
# 查询保存的图谱(调用者 | 被调用者 | 引用 | 邻居)
graphlens query my_function --graph graph.json --op callers
graphlens query MyClass.method --graph graph.json --op neighbors --depth 2
# 交互式 HTML 图谱查看器(在浏览器中打开)
graphlens visualize <project_root>
graphlens visualize ~/myrepo --lang python --show-external --max-nodes 500
graphlens visualize . --output graph.html --no-open
# 导出到 Neo4j
graphlens neo4j <project_root> --uri bolt://localhost:7687 --user neo4j --password secret
graphlens neo4j . --wipe --batch-size 200
# 通过模型上下文协议(MCP)服务器为代理提供图谱(需要可选的 `mcp` 额外功能:pip install "graphlens-cli[mcp]")
graphlens mcp --graph graph.json
```
### `mcp` — 模型上下文协议服务器
将保存的图谱暴露给 LLM 代理作为 MCP 工具:`graph_stats`、`find_nodes`、`callers`、`callees`、`references`、`neighbors`、`boundaries` 和 `communicates_with`。使用 `mcp` 额外功能并指向由 `graphlens analyze --output` 生成的 JSON 图谱。
### `visualize` — 交互式 HTML 图谱查看器
生成一个自包含的 HTML 文件,由 vis.js 提供支持,并在浏览器中打开。
| 标志 | 描述 |
|---|---|
| `--lang auto\|python\|typescript\|python,typescript` | 要使用的适配器(默认:自动检测所有) |
| `--show-external` | 包括 stdlib / 第三方外部符号节点 |
| `--show-structure` | 添加 `CONTAINS` / `DECLARES` 结构化边 |
| `--max-nodes N` | 剪枝低度节点超过 N(默认:1500) |
| `--output PATH` | 将 HTML 写入 PATH 而不是 `graph-<name>.html` |
| `--no-open` | 不要自动打开浏览器 |
**点击行为** — 点击任意节点以查看其信息面板。对于 `FUNCTION` 和 `METHOD` 节点,面板有一个 **"显示调用者"** 按钮,将图谱切换到焦点模式:仅显示选定的节点以及调用或引用它的每个节点,并在侧边栏显示调用者列表。点击空白空间或 **← 返回** 以返回完整图谱。
### `neo4j` — 导出到 Neo4j
使用 `UNWIND … MERGE` Cypher(无需 APOC 要求)。每个节点都获得一个 `:Code` 标签和一个特定于种类的标签(`:Function`、`:ExternalSymbol`,...)。关系按类型分组。安装可选的 `neo4j` 额外功能:
```bash
pip install "graphlens-cli[neo4j]"
```
## 图谱模型
### 节点种类
| 种类 | 描述 |
|---|---|
| `PROJECT` | 根项目节点 |
| `MODULE` | Python/TS/… 模块(目录或文件) |
| `FILE` | 源文件 |
| `CLASS` | 类声明 |
| `FUNCTION` | 顶级函数 |
| `METHOD` | 类中的方法 |
| `PARAMETER` | 函数/方法参数 |
| `VARIABLE` | 模块级或局部变量 |
| `ATTRIBUTE` | 类属性 |
| `TYPE_ALIAS` | 类型别名声明 |
| `IMPORT` | 导入语句 |
| `DEPENDENCY` | 声明的包依赖 |
| `EXTERNAL_SYMBOL` | 外部符号(stdlib,第三方或未知);携带 `metadata["origin"]` |
| `BOUNDARY` | 跨语言接口端口(HTTP 路由和客户端,消息队列主题,gRPC 方法,Temporal 活动);共享 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 路题和客户端,消息队列主题,gRPC 方法和 Temporal 活动。每个端口 |
### 链接图谱
适配器发出 `BOUNDARY` 端口,用于服务的接口或消耗的接口 - HTTP/REST 路由和客户端,消息队列主题,gRPC 方法和 Temporal 活动。每个端口都有一个语言无关的 ID(`make_boundary_id(mechanism, key)`),所以 Python FastAPI 路由和 TypeScript `fetch` 调用到同一个路径折叠成一个 `BOUNDARY` 节点,当它们的图谱合并时。`graphlens-link` 包然后将 `CONSUMES` 与 `EXPOSES` 匹配成 `COMMUNICATES_WITH` 边:
```python
from graphlens_link import link_graph
merged = python_graph.merge(ts_graph, allow_shared=True)
result = link_graph(merged) # 添加 COMMUNICATES_WITH 边
```
参见 `examples/demo_cross_language.py` 中的 Python 服务器 ↔ TypeScript 客户端的演练。
## 适配器插件系统
语言适配器通过 Python 入口点注册自己 - 不需要更改核心:
```toml
# packages/graphlens-python/pyproject.toml
[project.entry-points."graphlens.adapters"]
python = "graphlens_python:PythonAdapter"
```
核心注册表在运行时自动发现已安装的适配器:
```python
from graphlens import adapter_registry
adapter_registry.available() # ["python", ...]
adapter_cls = adapter_registry.load("python")
adapter = adapter_cls()
```
适配器也可以手动注册(适用于测试):
```python
adapter_registry.register("python", MyPythonAdapter)
```
## 实现适配器
继承 `LanguageAdapter` 并实现四个方法:
```python
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)
# ... 解析并填充图谱 ...
return graph
```
在 `pyproject.toml` 中注册并由核心注册表自动查找。
## 项目结构
```
graphlens/ ← uv 工作区根(核心库)
src/graphlens/ ← 模型,合约,注册表,异常,实用工具
packages/
graphlens-python/ ← Python 适配器(tree-sitter + ty)
graphlens-typescript/ ← TypeScript 适配器(tree-sitter + 编译器 API)
graphlens-go/ ← Go 适配器(tree-sitter + gopls)
graphlens-rust/ ← Rust 适配器(tree-sitter + rust-analyzer)
graphlens-link/ ← 跨语言链接器(COMMUNICATES_WITH)
graphlens-cli/ ← CLI(typer):分析,查询,可视化,Neo4j,MCP
tests/ ← 核心测试(100% 覆盖率)
examples/ ← 独立使用示例
```
## 开发
需要 Python 3.13+,[uv](https://docs.astral.sh/uv/),[task](https://taskfile.dev/)。
```bash
task install # uv sync --all-groups
task lint # ruff + ty + bandit 适用于所有包
task tests # 所有测试,覆盖率 100%
```
各个包任务:
```bash
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
连接信息
猜你喜欢
暂无推荐服务器