关于 CGraph
CGraph 将代码仓库提取为确定性代码图,面向需要理解大型项目结构的开发者和编码 Agent。它支持本地守护进程、轻量客户端及 MCP 服务,可查询符号关系、调用方和影响范围。
主要功能
- 01提取代码结构并构建代码图
- 02搜索符号、调用方和依赖影响范围
- 03查询节点邻域与最短路径
- 04生成受令牌预算约束的源码上下文
- 05导出交互图及模块视图
适用场景
- 01定位某个符号的调用方和传递依赖
- 02评估代码变更的影响范围
- 03为编码 Agent 提供精简的相关源码上下文
- 04浏览仓库模块与调用结构
MCP 相关
CGraph 提供 MCP 服务,让 Agent 直接查询代码图。客户端可使用图查询来导航符号关系,而不必逐个读取大量文件。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
目录
为什么选择 CGraph? · 架构 · 仓库布局 · 输出内容 · 输出格式 · 性能 · 检索基准测试 · 支持的语言 · 快速开始 · 安装与设置 · 与编码 Agent 配合使用 · CLI · 守护进程 · MCP 参考 · 宿主集成与增强 · 开发说明 · 贡献 · 许可证
为什么选择 CGraph?
要阅读大型代码仓库,才能回答**“是什么在调用它?”或“如果我修改它,哪些地方会受影响?”——这意味着要加载几十个文件,而答案并不在任何单个文件中,而是存在于文件之间的关系里。CGraph 会预先计算这些关系,让你(和你的 AI Agent)通过图谱而非 grep**进行导航。
| 🔗 反向依赖与影响范围 | graph_impact 可在约 10 ms 内返回某个符号的所有传递依赖项。在一个真实的、拥有 10,706 个节点的仓库中,它立即找出了某个庞大文件的 93 个依赖项——这是 grep 无法提供的答案。 |
| 🧭 导航,而非 grep | 按中心性排序的搜索、节点邻域、最短路径,以及按 token 预算组织的源码包——热数据可在约 10 ms 内提供,而无需读取许多会消耗上下文的文件。 |
| 🏛️ 一览架构全貌 | 中心性排序可找出承载关键功能的文件;Leiden/Louvain 社区检测会自动将仓库划分为模块。 |
| ⚡ 确定且快速 | 构建完整的 10k 节点图谱约需 2 秒;提取流程不使用 LLM——相同输入,每次都会生成相同图谱。 |
| 🤖 专为编码 Agent 打造 | 标准的 MCP 服务可将图谱直接交给 Claude / Codex,让它们依据结构进行推理,而非盲目读取文件。 |
**一句话介绍:**对于大到难以全部记在脑中的代码库,CGraph 就是决定读取哪些代码的索引——它预先计算了类似
ctags/“查找所有引用”的结果,按中心性排序,并通过 MCP 提供服务。
架构
cgraph— 一次性扫描 → 导出为可移植的磁盘文件(graph.json、graph.html、graph.svg、obsidian.md、cypher.txt、modules.mmd、modules.svg、design.mmd、design.md)。graphd+cgraph-client— 每个项目运行一个常驻守护进程,并实时监视文件;热数据查询query/path/explain/impact/context,响应时间约为 10 ms。cgraph-mcp— Model Context Protocol 服务,让 Agent 直接浏览图谱。
引擎负责确定性提取、片段验证、缓存状态和本地图谱变更;宿主负责选择模型和进行语义增强。
仓库布局
src/cli/ One-shot CLI entrypoint: cgraph
src/daemon/ Daemon entrypoint: graphd
src/client/ Thin client runtime and cgraph-client executable
src/mcp/ MCP request handling and cgraph-mcp executable
src/engine/ Detection, extraction, graph building, analysis, daemon ops
tests/smoke/ CTest smoke coverage for engine, daemon, MCP, and integration paths
tests/fuzz/ Optional libFuzzer targets
integrations/ Host hook and always-on integration scripts
docs/ Host integration contract and benchmark notes
vendor/ Vendored tree-sitter core and grammars
生成内容
单次扫描即可将源代码树转换为交互式、可探索的图——社区以不同颜色标示,枢纽节点的大小按中心性调整:

在含 10,708 个节点和 28,945 条边的代码库上运行的实际 graph.html 查看器——独立的 HTML,无外部 JS。
输出格式
graph.json— 带有图元数据、节点和链接的有向节点-链接 JSON。节点 ID 根据各文件相对于项目根目录的路径生成,因此,从两个检出目录构建的同一提交会得到相同的 ID,两个图可以通过 ID 合并;source_file保留读取该图时所用的路径graph.html— 可在浏览器中读取的交互式图视图graph.svg— 静态图可视化obsidian.md— 用于 Obsidian 风格导航的 Markdown 导出cypher.txt— Neo4j Cypher 语句modules.mmd— Mermaid 模块依赖图modules.svg— 分层静态模块依赖图design.mmd— 入口点及其主要调用流程,以 Mermaidflowchart TD表示design.md— 同样内容的 Markdown 版本:入口点表格、嵌套流程,以及按调用距离划分的层级
性能
交互式查看器过去每次打开时都会在浏览器中运行 O(N²) 力模拟。现在布局在 C++ 中预先计算一次(通过 igraph),并且重绘时会剔除视口外的内容——两者都无需依赖项:
| 图规模 | 稳定所需时间 之前 | 之后 | 每帧重绘 |
|---|---|---|---|
| 10,000 个节点 | 35.2 s(主线程卡死) | ~0 ms(静态布局) | 便宜 20–34×(缩放/平移) |
检索基准测试
CGraph 已在 Agent Retrieval Bench 上进行评分:来自 25 个代码库的 427 个样本,每个样本均检出到其基础提交,并使用基准测试自身的指标代码评分。下表涵盖全部 345 个正样本。
| 方法 | Recall@20 | MRR | BCY@8k |
|---|---|---|---|
| CGraph bin-v0.9.1(标签 + 代码文本搜索) | 0.6362 | 0.2787 | 0.4319 |
| CGraph bin-v0.8.3(仅标签文本搜索) | 0.5726 | 0.2356 | 0.3790 |
| RepoMap(已发布) | 0.6333 | 0.2158 | 0.3788 |
| RepoMap(在此测试框架上重新运行) | 0.6311 | 0.2156 | 0.3846 |
| Qwen3-Embedding-8B(已发布) | 0.7029 | 0.2336 | 0.3732 |
| Qwen3-Embedding-4B(已发布) | 0.6306 | 0.2379 | 0.3409 |
| BM25(已发布) | 0.4452 | 0.1520 | 0.2051 |
bin-v0.9.1 的对比情况:
- Recall@20。 与 RepoMap 持平(差异在约 0.006 的多次运行噪声范围内)。低于 Qwen3-Embedding-8B。高于其他已发布的单一方法。
- MRR。 高于所有已发布的单一方法。
- BCY@8k。 高于所有已发布的单一方法,且差距超过噪声下限。
- 与论文中 Qwen3-Embedding-8B 和 RepoMap 的融合方法相比,在其报告的 287 个核心样本上:MRR 持平(融合方法只对前 20 个结果评分;CGraph 前 20 个结果的 MRR 为 0.276,对方为 0.271,差异在噪声下限内);Recall@20(0.633 对 0.733)和 BCY@8k(0.440 对 0.488)较低。
- 弃答。 对于没有金标准结果的样本,CGraph 不会弃答。
各任务的表格、固定版本、噪声下限和注意事项见
bench/arb/RESULTS.md。使用 bench/arb/run.sh 重现。
语言
基于 Tree-sitter 的结构化提取,并对少数配置格式使用正则表达式/结构化提取:
此外,还支持对 Apex、Delphi 窗体/源文件、MSBuild/XML 项目文件和 MCP 配置文件进行结构化/正则表达式提取。
类型成员
七种语言会为每个声明的成员生成一个 field 节点,并从所有者节点建立一条 defines 边:
| 语言 | 包含成员的所有者 | 字段属性 |
|---|---|---|
| C、C++ | 仅 class、struct,不包括 enum 或 union | 无 |
| TypeScript、TSX | interface、对象类型别名、class(包括构造函数参数属性)、enum | type_text、optional、readonly |
| Go | struct,包括多名称字段和嵌入字段 | type_text |
| Rust | 命名和元组 struct、union、enum 变体、trait 关联类型和常量 | type_text |
| Python | 类体赋值,包括链式和元组目标 | 带注解时为 type_text |
| Java | class、record、enum | type_text、readonly |
成员仅指已声明的成员:不会展开继承成员,不会扁平化别名,也不会推断运行时属性。已经作为独立节点存在的声明——例如 TypeScript
method_signature、Rust trait 方法或类型别名——仍保留其函数或类型节点,不会同时作为字段。字段不会占用函数或类型已有的 id;它会移动位置,因此符号的
id 保持稳定。
没有函数体的类式声明(struct FileCacheEntry;)属于前向声明,不会生成节点;只有带函数体的定义才会生成节点。
字段是成员,不是导航目标:graph_context 会打包所有者(其代码片段涵盖所有成员),而不是成员本身;report modules 的符号计数也不包括字段。
HTTP 端点
JavaScript 和 TypeScript 服务会为代码提供的每条路由生成一个 endpoint 节点,并跨文件组合完整路径:Elysia、Express 或 Hono 链自身的前缀(new Elysia({ prefix: '/notebooks' })、.basePath('/v1')),会叠加在每个挂载它的 .use(child)、.use('/p', child) 或
.route('/p', child) 之下,直到顶层链;解析时会穿透 as any 和 as unknown as T 类型转换,并通过导入解析(包括别名导入)。在 .use(new Elysia({ prefix }).get(…)) 中内联构建的链、.group('/v2', app => app.get(…)) 或 .guard() 回调,以及通过 export const deckModule = deckRoutes as unknown as Elysia 重新导出的模块,组合方式都相同。导出 GET 的 Next.js app/api/x/[id]/route.ts 对应 GET /api/x/:id。包含非空 graphs 对象的 langgraph.json 仓库是 LangGraph Agent Server:它的 49 条框架路由(POST /runs/wait、POST /threads/{thread_id}/runs/stream、GET /assistants/{assistant_id}、……,即表格 @langchain/langgraph-api 1.5.1 注册的路由,减去那四条会返回 500“Not implemented”的 crons 路由;参见
https://docs.langchain.com/langsmith/server-api-ref)是由一个覆盖该对象的 langgraph_server 节点处理的服务端点,每个图对应一个 langgraph_graph 节点。配置关闭的组(http.disable_runs、disable_store 等)不会被提供。
Kotlin 和 Java Spring 控制器也会生成相同的节点:类级别的 @RequestMapping("/api/v1/users") 下,带有 @GetMapping("/{id}")(或 @PostMapping、@PutMapping、@DeleteMapping、@PatchMapping,或带 method 的 @RequestMapping)注解的方法对应 GET /api/v1/users/{id}。路径只从字符串字面量读取(位置参数、value =、path =,或它们的数组);由常量构建的路径,以及不带 method 的方法级 @RequestMapping,不会生成端点。
若 Spring Boot 应用的 build.gradle(.kts) 或 pom.xml 应用了 Boot 插件(不是 apply false;Maven:使用 Boot 父级,或在 <pluginManagement> 之外使用该插件,且打包类型不是 pom),并依赖 spring-boot-starter-actuator 和一个 Web starter,则会提供其 src/main/resources/application[-profile].{yml,yaml,properties} 中公开的 Actuator 端点:默认是 health;否则是 management.endpoints.web.exposure.include 减去 exclude,路径位于 management.endpoints.web.base-path 下(默认为 /actuator),并包括发现页面、健康组、逐端点访问控制;只有配置了 Prometheus registry 时才包括 prometheus,只有配置了缓存模块时才包括 caches。Boot 4 默认启用存活和就绪组(除非 probes.enabled 为 false);Boot 3 或版本未知时,只有在该值为 true 时才启用。这些端点的位置取决于 Boot 的 ManagementPortType:负数 management.server.port 或 spring.main.web-application-type: none 表示不提供任何端点;相同端口时,端点位于上下文路径下;不同端口时,则位于 management.server.base-path 下。端点是基础配置与每个 profile 中端点的并集,由 include 生效位置上的 actuator_exposure 节点处理(如果没有任何配置设置它,则由构建文件中的 spring_actuator 节点处理)。需要其他 bean 或依赖的端点、profile 表达式、没有默认值的占位符,以及决定键上使用了 YAML 锚点、别名、标签或流式映射的配置均不建模(这些配置不会提供任何端点);参见 spring_actuator.hpp。
Python FastAPI 服务也会生成端点:在模块级别的 router = APIRouter(prefix="/project") 或 app = FastAPI() 上,使用 @router.post("/{project_id}/setup")(或 .get、.put、.patch、.delete、.head、.options,或默认使用 GET 的 .api_route(path, methods=[...]))装饰的函数,对应 POST /project/{project_id}/setup;若它被 include_router(router, prefix="/v1") 或 app.mount("/v1", sub_app) 挂载,则会在其路径前组合相应前缀,并可通过跨文件导入解析:from api.routes.x import router as x_router(别名是唯一绑定的名称)、from app.routers import users 后接 include_router(users.router)(子模块)、import app.routers.items as items 后接 include_router(items.router)、由包的 __init__.py 导入路由器并由另一文件从包导入,以及对字面量元组或列表进行 for r in (users.router, items.router): app.include_router(r) 循环时的每个路由器。不是普通字符串字面量的路径、前缀或 methods 列表(例如包含插值的 f-string、prefix=settings.PREFIX),函数内部的路由或 include_router(应用工厂),以及对非本文件模块级路由器调用的 include_router(app = create_app()、app.router、self.app、从其他文件导入的路由器)均不会生成端点,并计入 routes_unresolved / mounts_unresolved;所有挂载都属于上述情况的路由器不会生成任何端点。循环对象不是其他路由器时(for r in ROUTERS:),会计为未解析,但不会识别其中的路由器。没有被挂载的路由器会在自身前缀下提供服务,与 JavaScript 路由器相同。不读取 add_api_route(...);挂载较长的点分名称(include_router(api.v1.router))会计为未解析,但不会放置其路由器。不会读取 Flask:register_blueprint(url_prefix=) 会替换 blueprint 自身的前缀,而非与其组合。
节点的 ID 格式为 endpoint:GET /api/v1/notebooks/{}/notes,其中不含仓库信息,且每个参数段(:id、{id}、[id])都用 {} 表示;标签保留提供方的拼写形式(GET /api/v1/notebooks/:id/notes)。节点携带 method 和 path、处理器的文件与跨度,以及从该文件指向节点的 contains 边和指向处理器的 handled_by 边,因此对处理器执行 graph_impact 可以到达其端点。同一链挂载两次,其路由也会提供两次;如果文件仅通过函数参数接收路由器(function register(app) { app.get(…) }),则不会生成端点,并会在 stats.json 的 route_resolution.routes_unresolved 中计数。
数据库表也会纳入同一遍遍历。仓库 .sql 迁移中的每个 CREATE TABLE 都会成为一个 sql_table 节点,而使用字面量名称声明的 Drizzle 模型(export const competitors = pgTable('competitors', …),以及 mysqlTable 和 sqliteTable)会通过 maps_table 边关联到同名表。通过导入到达某个文件,或通过该文件声明的模块级值到达该文件的依赖项遍历,也会到达该文件所服务的端点,因此,对 sql_table_competitors 执行 graph_impact 会依次遍历表、模型、导入模型的服务、这些服务的路由文件,以及路由。经过函数、类或端点的路径不会关联到路由,无论它是向上遍历到函数所在的文件,还是跟随函数的导入(并继续到挂载路由文件的应用文件):变更的函数会通过对路由处理程序的调用到达其影响的路由,而不是附近的所有路由。到达 seam service 节点时会将其列出,但不会继续遍历,因此,只有一条消费者边指向该节点,不会带出该服务的所有端点。
调用方也位于图中。fetch(\${API_URL}/api/v1/projects/${id}/publish`, { method:
'POST' })、openapi-fetch 的 api.GET('/api/v1/projects/{id}', …)、axios.post(…),以及通过路径包装器发出的调用(apiFetch(path),其自身的 fetch(`${base}${path}`)会将参数追加到模块常量),都会为调用函数(或箭头函数初始化的模块级对象)添加一条指向endpoint: 的CONSUMES边。主机插值会被忽略(但接收运行时值的调用除外,例如${base(id)}/x;这种 URL 构建器可能包含部分路径,如果它不是同一文件中的函数(此时会读取该函数),请求就会保持未解析状态);完整路径段的插值会变成 {};方法取自调用中字面量形式的 method 选项、包装器自身的方法或客户端动词,否则默认为 GET。能从单个文件中确定的信息都会被读取:存放在局部变量中的 URL(const url = `${BASE}/x`、new URL(...)后调用url.toString()、按分支设置的 let)、类字段前缀(${this.API_BASE}/v1/...)、axios 实例的 baseURL(在 baseURL: '/api/backend'下调用this.api.get('v1/users'))、类方法包装器(this.patch(`v1/users/${id}/enable`)),以及路径或方法作为后续参数传入的包装器(mlBackendRequest('POST', `/project/${id}/setup`);调用省略方法参数时,会应用方法参数的默认值)。如果此仓库不提供该路由,节点会标记为 served: false,且没有来源;如果提供(从同一应用获取的 Next.js 路由文件),同一节点会同时包含处理程序及其调用方。文件无法读取其值的 URL、调用中未明确写出的方法、没有以 / 结尾的基路径可供拼接的相对路径(v1/users),或字面量形式的绝对 https:// URL,都不会添加任何内容,并会计入 route_resolution.calls_unresolved。Kotlin 和 Go 客户端也以相同方式使用路由:
Ktor 的 client.patch("$baseUrl/api/v1/sessions/$id/invalidate") { … }(接收者名称以客户端形式结尾,如 client、httpClient、api,但不包括 clients 或 httpCache),Go 的 http.Get(url) 和 http.NewRequest(method, url, body),以及任何按顺序传入 context、方法(http.MethodPost 或 "POST")、路径的 Go 调用,例如 c.Do(ctx, http.MethodGet, "/api/v1/auth/me", nil)。Go 路由注册(r.Handle(http.MethodGet, "/x", h))、带有 func 字面量参数的调用、httptest 和对 r.Method 的断言都没有 context 或处理程序,因此不属于请求。Ktor URL 中开头的 $baseUrl 是主机;Go 客户端方法的路径相对于其自身的基路径,因此路径前有其他值(n.Base+"/import")时,该调用会保持未解析状态。请求会追加某个参数的函数(postAuth(ctx, path, req) 调用 c.Do(ctx, http.MethodPost, path, body),或 client.post("$baseUrl$path"),也包括在 lambda 内部调用的情况,例如 withContext(…) { … })属于包装器;同一文件中带有路径字面量的调用(c.postAuth(ctx, "/api/v1/auth/login", req)、postLoginOutcome(path = "/api/v1/…"))会使用拼接后的路由。Ktor 的 client.request(url) { method = … } 在 builder 中设置动词,因此会计为未解析。由于 id 不包含仓库信息,分别构建的两个图会共享其端点节点。seam fuse 会按服务范围限定其他所有节点的 id(api::src_db_client_ts),因此相对文件路径相同的两个仓库仍会成为两个节点:
cgraph seam discover --graph api=api-out/graph.json --graph web=web-out/graph.json --out seam-drop
cgraph seam fuse --seam seam-drop/chunk_00.json --graph api=api-out/graph.json --graph web=web-out/graph.json --out fused
seam discover 会根据每个图提供(SERVED_BY、HANDLED_BY)、使用(CONSUMES、CONSUMED_AT)和记录(DOCUMENTED_IN)的内容生成 seam 片段,无需手写规范;它还会报告跨服务匹配的端点数量、在图中没有提供方却被使用的端点数量,以及当图中包含契约文档时的偏差:文档承诺但没有服务提供的端点,以及服务提供但文档未提及的端点。
前端通过自己的兜底代理访问后端(idp-front-end 调用
/api/backend/v1/users/{id};其 app/api/backend/[...path]/route.ts 会转发到
${BACKEND_URL}/api/v1/users/{id}),需要在 seam discover 和 seam fuse 中都使用
--prefix REPO:/from=/to 来命名(--prefix web:/api/backend=/api)。端点 web 在 /api/backend 下消费但不提供服务,随后与提供方的 /api/... 端点合并;其 CONSUMED_AT 边会将消费者自身的路径保留为 via,日志则统计每个前缀合并的端点。前端自行提供的路由(/api/backend/healthz)永远不会被映射,而只有前端提供服务的代理路径也不会合并。每个仓库自己的图谱都保留自身的路径写法。转发目标不会从代理代码中读取:它在运行时根据处理程序的参数构建,因此是声明出来的,而非猜测得出的。
提取器会将端点以外的契约记录为 provides_contract / uses_contract 事实:header:<name>(大小写折叠)、claim:<name>、env:<NAME>、DynamoDB 表为 dynamo:<name>,gRPC 方法为 grpc:<Service>/<Method>,消息主题和队列为 topic:<broker>:<name>,表和图标签为 table:<database>:<name> / label:<database>:<name>。它们的合并方式并不相同:
- Claims and non-standard headers(
x-tenant-id)会像端点一样按 ID 关联。标准 HTTP 标头(authorization、content-type、任何 IANA 永久字段名称、x-request-id、x-forwarded-*、traceparent)不会关联:每个服务都因自身原因使用这些标头。标准 JWT 声明也不会关联,因为任何签发方写入它们时含义都相同(iss、sub、exp、email、name、scope、client_id:IANA JWT Claims 注册表中的 RFC 7519、OpenID Connect、 RFC 7800、RFC 8693 和 RFC 9449 名称);应用声明(roles、tenant_id、session_id)则会关联。 单凭声明名称无法判断它属于哪个令牌,因此可以声明由同一签发方签发令牌的仓库:--issuer idp=idp,web,cli会将它们的claim:<name>写作claim:idp:<name>。这些仓库之间,除 RFC 7519 注册声明(iss、sub、aud、exp、nbf、iat、jti)外,所有声明都会关联,因此 OpenID Connect 声明 (email、name、scope)也会在这些仓库之间关联,且它们的声明永远不会与签发方之外的仓库关联。 未声明签发方时,声明按上述规则关联。只有能够明确证明代码涉及 JWT 的位置才会记录声明:jjwtJwts.builder()或 NimbusJWTClaimsSet.Builder()链上的.claim("roles", r),jsonwebtokensign、 joseSignJWT和 PyJWTjwt.encode的载荷键;嵌入 golang-jwtRegisteredClaims、传入ParseWithClaims或从令牌载荷片段反序列化的 Go 结构体的json:"x"标签;解码器返回的 TypeScript 类型的属性(jwtDecode<T>,或 拆分、base64 解码并对片段 1 执行JSON.parse的函数),以及任何文件中从此类解码器结果读取的v.roles;手动解码载荷中的 KotlinjsonObject["x"]。仅仅名为roles或session_id的字段不是声明。 - Tables and graph labels 只会在已声明的数据库范围内关联。代码中没有信息表明哪些仓库共用一个数据库,因此没有已知数据库的表会记作
table:local:<name>,仅限其所在仓库使用;直到--database turing=turing-api,ml-backend声明二者都使用数据库turing,其表才会归为table:turing:<name>,不会与其他仓库的表关联。 产生这些条目的方式(src/engine/data_contracts.cpp):.sql文件创建的表,以及描述该表的 DrizzlepgTable/mysqlTable/sqliteTable模型会提供table:;如果仓库中没有.sql文件创建某个表,Drizzle 模型仍会作为镜像架构使用该表,任何将该模型传给db.update(...)、.from(...)、.innerJoin(...)等函数也会使用该表。Python、TypeScript/JavaScript、Kotlin 或 Java 字符串中命名的表,只有当字符串(相邻字面量和+链会合并)以大写SELECT/INSERT/UPDATE/DELETE/WITH开头,且表名跟在大写FROM/JOIN/INSERT INTO/UPDATE ... SET后面时才会使用该表(会跳过开头的--//* */注释)。users{where_sql}、users$filter和"SELECT * FROM users" + where会读取users;在插值或+之前以_结尾的名称,或在其之后继续拼接的名称(events_{year}、events_%s、t{y}_x)不会读取。小写 SQL、JPA@Query中的 JPQL(没有nativeQuery = true)以及测试文件(tests/、__tests__/、*.test.ts、test_*.py、FooTest.kt等)不会读取。Spring Data Neo4j@Node("User")会提供label:User,其上的出向@Relationship(type = "HAS_ROLE")会提供label:User.HAS_ROLE;.cypher文件(不会检测.cql:Cassandra 也使用它)以及以MATCH/OPTIONAL MATCH/MERGE/CREATE (/UNWIND开头的字符串中的 Cypher 会使用这些标签;只有起始节点的标签已知时才会使用关系。 - DynamoDB tables(
src/engine/dynamo_contracts.cpp)无需声明,按名称关联:表名在 AWS 账户中就是其完整地址,且它永远不会是table:,因此不能与同名 Postgres 表关联。在导入 DynamoDB SDK 的 TypeScript/JavaScript 文件中,写入操作(PutItemCommand、UpdateItemCommand、DeleteItemCommand、lib-dynamodbPutCommand、DocumentClientput/update/delete等)会提供dynamo:<name>,读取操作(GetItemCommand、QueryCommand、ScanCommand、get/query/scan)会使用它。名称可以是字面量、process.env.X || 'name'中的默认值,或保存上述任一值的同文件const;环境变量会保留为节点的env。一个变量读取时带有两个默认值,会对应两个表(turing-agents-dev和wiki-agent-memory会保持分离)。this.tableName、参数、没有默认值的环境变量读取、批量请求和测试文件都不会记录。不会读取 Python(boto3)。注意:代码中没有信息表明服务使用哪个 AWS 账户或区域,因此dynamo:名称会与工作区或 seam 中任何提及该名称的仓库关联,而不论各自运行在哪个账户中:两个位于不同账户、各自拥有sessions表的服务也会关联。可选的账户声明会作为后续事项记录。 - gRPC methods and topics(
src/engine/messaging_contracts.cpp)无需声明,按名称关联。gRPC 方法为grpc:<Service>/<Method>,不包含 proto 包名(Go 的pb.NewCartServiceClient或 Python 的CartServiceStub调用本身不会写出该名称);方法名称按 proto 中的写法(Java 和 JavaScript 的getCart以及 C# 的GetCartAsync均读取为GetCart)。服务端会提供该方法:实现生成基类的类中的方法(Java<S>ImplBase、C#<S>.<S>Base、Python<S>Servicer、C++<S>::Service)、NodeaddService(<S>.service, {...})中的键,或 Go 中通过Register<S>Server注册类型的处理方法,其一元调用形式和消息来自注册所用的包。生成客户端上的调用会使用该方法:GoNew<S>Client、Java<S>Grpc.new*Stub或<S>BlockingStub字段、Python<S>Stub、C#<S>.<S>Client、导入了@grpc/grpc-js的 TypeScriptnew <S>Client;仅当文件显示其中有生成的 gRPC 代码时才适用(生成的 stub 导入路径、*_pb2_grpc模块、代码中命名的<S>Grpc类、Grpc.*命名空间);通过局部变量、字段或绑定到这些对象的self/this成员调用也适用,但仅限文件中该名称未绑定到其他对象的情况。gRPC 自带的服务(Health、ServerReflection、Channelz)和 OTLP 服务永远不会被记录。主题为topic:<broker>:<name>(kafka、rabbitmq、sqs、sns、pubsub、jms):生产者会提供它,消费者会使用它(Java/Kotlin/C#/Go/Python/JavaScript 中的 Kafka、Spring Kafka 和 Kafka Streams、默认交换机上的 RabbitMQ、SQS 队列 URL、SNS 主题 ARN、Pub/Sub 主题),且调用的接收者必须是该代理的客户端。名称可以是字面量、环境变量读取的字面量默认值,或文件中每个绑定都属于上述字面量之一的名称;如果名称也是参数、循环变量、模式,或绑定到其他表达式,则不会读取。注意:若两个 proto 包都定义了CartService,它们会关联到同一个 ID。messaging_contracts.hpp列出了读取的所有模式。 - Env names 只有在声明后才会关联:
--env ML_BACKEND_URL=ml-backend会为该变量指定其所访问的服务(SERVED_BY)。未声明的变量(NODE_ENV)会保留在其所在仓库中。--database、--env和--issuer标志用于seam discover和seam fuse,且两者必须使用相同的值:如果加入 seam 时使用的声明未传给 fuse,则会拒绝该 seam,而不会悄悄将其拆分。
合约文档
文档中声明的合约也会被读取。OpenAPI JSON 文档(openapi*.json、swagger*.json;不读取 YAML)会为每个路径和方法生成一个文档化的 endpoint,并为每个组件 schema 生成一个 schema,其中每个属性对应一个 field,它们通过 RESPONDS_WITH、ACCEPTS、references 和 inherits(allOf)关联。.proto 文件会为每个消息和枚举生成一个 schema,为每个服务生成一个 type,并为每个 rpc 生成一个端点 POST /<package>.<Service>/<Method>。.graphql schema 会为每个类型、接口、输入、枚举、联合类型和标量生成一个 schema,并为每个根操作字段生成一个端点 QUERY <field> / MUTATION <field> / SUBSCRIPTION <field>。TypeScript 客户端所依据的 openapi-typescript 输出(包含 operations 和 components 的 export interface paths)也会以相同方式读取,其包含 455 个成员的 paths 接口不再产生 455 个字段节点。文档化端点与已提供或已使用的端点具有相同的规范 ID,因此它们会合并为一个节点,同时包含 handled_by、CONSUMES 和文档锚点;stats.json 会在 route_resolution.endpoints_documented 下统计这些端点。Schema 是 report types 中的类型所有者,因此 API schema 和手写的 TypeScript 镜像会显示为重复项或相同结构。没有任何此类文件的仓库不会获得任何收益。
工作区
seam discover 可离线连接两个图;工作区则可实时连接它们。在某个目录中放置一个 cgraph.workspace.json,列出其成员仓库,然后将任意工具指向该目录,而不是某个项目:
cgraph workspace init --root ~/work --repo api=./turing-api --repo web=./turing-webapp
cgraph workspace status --root ~/work
cgraph-client --root ~/work impact '{"id": "<the handler>", "direction": "dependents"}'
每个仓库都保留自己的守护进程、监视器和增量更新;不会将任何内容复制到工作区进程中。联合 impact 会向每个成员查询种子节点;当遍历到 endpoint: 节点时,会将该契约及剩余深度转发给其他仓库一次。因此,更改 API 处理器时,会报告调用它的前端钩子,每个见证结果都标注其 repo 以及所经由的契约。path 会以相同方式在契约处连接两个仓库,query 和 explain 会合并结果并添加标注,update 会向所有成员分发;不带 --repo 的 workspace init 会发现下一级目录中的所有 git 仓库。清单中可选的 prefixes([{"repo": "web", "from": "/api/backend", "to": "/api"}])会将相同的代理映射传递给 impact 和 path,使其从 web 的 /api/backend/... 占位路径跨到 backend 的 /api/... 端点,反向亦然;跨越此映射的 path 会保留两种写法。对 web 自身路由的更改不会触及 web 中代理路由的调用方,因为这些调用方访问的是 backend 中的副本。可选的 databases([{"name": "turing", "repos": ["api", "ml"]}])、env([{"name": "ML_BACKEND_URL", "service": "ml"}])和 issuers([{"name": "idp", "repos": ["idp", "web"]}])用于声明连接关系。impact 和 path 会像跨越端点一样跨越声明和非标准标头,但不会跨越标准 HTTP 标头;只有 env 声明的环境变量名才能作为跨越点;成员的 table:local: ID 只会连接到同一数据库中的其他成员;签发者成员的声明只会连接到同一签发者中的其他成员。守护进程关闭的仓库会出现在 unreachable 中,而不会从答案中消失。report、context 和记忆相关操作会按项目分别作答,并说明这一点及应使用的根目录。若根目录是工作区,MCP 服务也会执行联合查询,无需新增工具。
在某个成员仓库(或嵌套于其中的工作树)内打开的工具,会在父目录中逐级查找 cgraph.workspace.json,最多查到 $HOME;只有当清单列出该仓库时才会使用该工作区。在这种情况下,impact 和 path 会跨越工作区(答案中包含 workspace: {name, home}),而 query、explain、context 和 status 仍会在主仓库中执行,并基于打开工具时所在的代码树作答。cgraph change-context 和 MCP graph_change_context 会添加一个 cross_service 部分:对于更改所服务的每个端点(编辑的端点、遍历到的处理器对应的端点,或位于更改文件中的端点),列出其他仓库中的直接调用方;对于其调用的每个端点(来自已更改代码,或来自调用已更改辅助函数的函数),列出为其提供服务的处理器。该部分有自己的四分之一预算,不会为了腾出空间容纳影响信息而被舍弃;被裁剪的内容会计入 omitted.cross_service,并列出所有无法作答的仓库(unreachable)或仍在构建的仓库(building)。因此,空的 rows 表示 CGraph 无法解析任何调用方,并不代表没有进行查询;除非该部分因预算而被截断为 stub,此时 rows_found 会说明找到的数量,或者因无法使用工作区清单而包含 errors。无法解析的调用会计入 route_resolution.calls_unresolved,显示在 stats.json 和每个守护进程的 status 中(随图保存,因此重启守护进程后仍会报告)。更改路由路径或挂载点会视为移除旧路由,因此会列出其调用方。
快速开始
Claude Code 插件
claude plugin marketplace add Nxtsoft/CGraph
claude plugin install cgraph@cgraph
此插件会注册 cgraph MCP 服务、cgraph 和 cgraph-enrich 技能,以及预编辑钩子。Claude Code 会在每次请求中列出所有已注册的 MCP 工具和所有可由模型调用的技能,因此此插件会尽量缩短列表:服务会列出 graph_query、graph_context、graph_impact 和 graph_status,而只有在你输入 /cgraph-enrich 时才会运行 cgraph-enrich。启动 Claude Code 前,设置 CGRAPH_MCP_TOOLS=all(或以逗号分隔的工具名称列表)即可列出更多工具。当 Claude Code 在 git 仓库根目录(包含 .git 的目录)或 CGraph 之前已索引过的项目中启动时,会在会话开始时启动守护进程,因此 Agent 读取任务时,图谱便已开始构建,预编辑钩子也有守护进程可供查询。守护进程会写入 cgraph-out/,并在其中创建一个 .gitignore,忽略该目录中的所有内容,因此 git status 保持干净。在其他位置,例如仓库的子目录或非 git 仓库的主目录中,首次调用工具时才会启动守护进程。首次使用时,它会下载适用于你平台(Linux x64/arm64、macOS arm64/x64)的发行版归档文件,根据 integrations/launcher/release.json 校验其 SHA-256,然后将其解压到插件的数据目录中。PATH 中需要有 node 和 tar。
Homebrew
适用于 macOS(Apple 芯片或 Intel)或 Linux x64:
brew install nxtsoft/cgraph/cgraph
# Run this from any source repository.
cgraph --root . --out cgraph-out
该 formula 会从当前发行版归档文件安装全部四个可执行文件。tap 为
Nxtsoft/homebrew-cgraph;其中的 Formula/cgraph.rb 对应本仓库中的 packaging/homebrew/cgraph.rb。
发行版归档文件
下载当前 Linux x64 发行版并构建你的第一个图谱:
mkdir -p "$HOME/.local/lib/cgraph/bin-v0.9.7" "$HOME/.local/bin"
curl -fL https://github.com/Nxtsoft/CGraph/releases/download/bin-v0.9.7/cgraph-linux-x64.tar.gz \
-o "$HOME/.local/lib/cgraph/bin-v0.9.7/cgraph.tar.gz"
tar -xzf "$HOME/.local/lib/cgraph/bin-v0.9.7/cgraph.tar.gz" \
-C "$HOME/.local/lib/cgraph/bin-v0.9.7"
for name in cgraph graphd cgraph-client cgraph-mcp; do
ln -sf "$HOME/.local/lib/cgraph/bin-v0.9.7/$name" "$HOME/.local/bin/$name"
done
export PATH="$HOME/.local/bin:$PATH"
# Run this from any source repository.
cgraph --root . --out cgraph-out
在浏览器中打开 cgraph-out/graph.html(在 macOS 上运行 open cgraph-out/graph.html),然后将 CGraph 注册到你的编码 Agent。其他架构和源码构建方式请参阅安装与设置。
安装与设置
发行版 bin-v0.9.7 的每个归档文件均提供全部四个可执行文件(cgraph、graphd、cgraph-client 和 cgraph-mcp):
| 平台 | 架构 | 归档文件 |
|---|---|---|
| Linux | x86_64 / amd64 | cgraph-linux-x64.tar.gz |
| Linux | arm64 / aarch64 | cgraph-linux-arm64.tar.gz |
| macOS | Apple 芯片 / arm64 | cgraph-macos-arm64.tar.gz |
| macOS | Intel / x86_64 | cgraph-macos-x64.tar.gz |
使用 uname -s 和 uname -m 选择归档文件。快速入门会将带版本号的文件安装到 ~/.local/lib/cgraph/bin-v0.9.7,并在 ~/.local/bin 中创建稳定的符号链接;如有需要,请将该目录添加到 PATH。MCP 客户端配置应使用带版本号的绝对路径,因为客户端可能无法继承 shell 的 PATH。
从源码构建
完整构建步骤 — 前置条件 · vcpkg · PATH · sanitizer 和 fuzzer 预设
前置条件
- CMake 3.25 或更高版本
- Ninja
- C++20 编译器(较新版本的 Clang 或 GCC;Xcode Command Line Tools 中的 Apple Clang 也可使用)
- Fortran 编译器(例如
gfortran)——igraph的 vcpkg 构建会引入lapack-reference,而它需要 Fortran 编译器(sudo apt-get install -y gfortran/brew install gcc) - Git
- vcpkg(使用本地副本即可——见步骤 2)。
curl、igraph、nlohmann-json和utf8proc已在vcpkg.json中声明,并会在首次配置时构建。tree-sitter已随项目包含在vendor/tree-sitter下。
1. 克隆(含子模块)
git clone --recurse-submodules https://github.com/Nxtsoft/CGraph.git && cd CGraph
# already cloned without submodules?
git submodule update --init --recursive
2. 指定 CMake 使用的 vcpkg
git clone https://github.com/microsoft/vcpkg .vcpkg # full depth — a shallow clone omits the pinned baseline
./.vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT="$PWD/.vcpkg"
3. 配置、构建并验证
cmake --preset release
cmake --build --preset release # first build compiles vcpkg deps — several minutes
ctest --preset release # smoke suite
build/release/src/cli/cgraph --root . --out cgraph-out
二进制文件位于 build/release/src/{cli/cgraph, daemon/graphd, client/cgraph-client, mcp/cgraph-mcp}。
4.(可选)将二进制文件添加到 PATH
mkdir -p ~/.local/bin
for b in cli/cgraph daemon/graphd client/cgraph-client mcp/cgraph-mcp; do
ln -sf "$PWD/build/release/src/$b" ~/.local/bin/
done
MCP 客户端配置(见下文)仍应使用二进制文件的绝对路径,因为客户端可能无法继承交互式 shell 的
PATH。
开发版本
cmake --preset default && cmake --build --preset default && ctest --preset default # Debug, no -O
cmake --preset sanitizers && cmake --build --preset sanitizers && ctest --preset sanitizers # ASan/UBSan
cmake --preset fuzzers && cmake --build --preset fuzzers && ctest --preset fuzzers # libFuzzer
fuzzer 预设需要带有 libFuzzer 运行时的 Clang 工具链;如果 Apple Command Line Tools 不包含该运行时,请使用上游 LLVM/Clang 工具链。
与编码 Agent 配合使用
cgraph-mcp 是一个通过 stdio 通信的标准 MCP 服务(协议 2024-11-05)。注册一次后,Agent 就可以通过快速的图查询浏览代码库,而不必盲目地使用 grep/read:
| 工具 | 用途 |
|---|---|
graph_query | 按文本搜索节点;按中心性排序 |
graph_explain | 查看节点的邻域(调用方、被调用方、导入项) |
graph_impact | 更改节点的传递影响范围 |
graph_path | 查找两个节点之间的最短路径 |
graph_context | 为节点/查询生成受 Token 预算限制的源代码包(支持自适应收集) |
graph_report | view: "modules":模块依赖关系图(层级、循环、导入/调用计数);view: "types":相同、重复、重叠及未引用的类型定义;view: "clones":按类别分组的近似重复函数体;view: "design":入口点、主要调用流、按调用距离划分的层级;所有结果均受预算限制 |
graph_update | 基于内容验证的同步;返回一个 content_root,用于固定读取内容 |
graph_status | 守护进程、图和增强状态 |
graph_change_context | 针对两个源代码根目录之间的显式统一差异,提供影响分析和受预算限制的上下文 |
graph_remember / graph_recall | 会话记忆——在 /compact 前保存检查点,之后进行回忆;回忆时会标记代码已更改的检查点(详情) |
graph_shutdown | 停止守护进程 |
graph_context 有两种收集模式。默认模式(gather: "adaptive")保留完整的 2 跳核心,仅沿与查询相关的节点扩展第 3 跳——在检索评测中,它使 2 级召回率提升了 +0.057,候选 Token 数仅增加 +13%;相比之下,完整的 3 跳收集会增加 +96%(传入描述任务的 query/q)。gather: "fixed" 则打包普通的 k 跳邻域。
服务会依次从 --root、CLAUDE_PROJECT_DIR 和工作目录确定项目根目录,并自行查找 graphd(显式指定的 --daemon 优先,其次是 CGRAPH_DAEMON_PATH,最后是 cgraph-mcp 旁边的 graphd)。首次调用会触发一次性构建(耗时数秒)。图读取最多等待 30 秒;对于工作区,整个请求最多等待 30 秒。如果结果仍带有 "graph_state": "building",说明构建尚未完成,因此结果较短;这样就不会把空结果误认为“没有匹配项”。后续查询会使用热缓存(约 10 ms)。以下示例中,请将 /home/you 替换为你的绝对主目录。
🔌 注册到 Claude Code · Codex · Cursor / Windsurf / 其他 MCP 客户端
Claude Code
Claude Code 会为每个会话设置 CLAUDE_PROJECT_DIR,因此只需注册一次,即可用于所有项目:
claude mcp add --scope user --transport stdio cgraph \
-- /home/you/.local/lib/cgraph/bin-v0.9.7/cgraph-mcp \
--daemon /home/you/.local/lib/cgraph/bin-v0.9.7/graphd
也可以在仓库根目录提交项目专属的 .mcp.json,与协作者共享:
{
"mcpServers": {
"cgraph": {
"command": "/home/you/.local/lib/cgraph/bin-v0.9.7/cgraph-mcp",
"args": ["--daemon", "/home/you/.local/lib/cgraph/bin-v0.9.7/graphd"]
}
}
}
在 Claude Code 中运行 /mcp 进行验证。此仓库还在 integrations/skills/ 下提供主机技能——cgraph(遇到结构问题时优先使用图)和 cgraph-enrich(语义增强工作流)。使用 cgraph skills install 安装;使用 cgraph drain install 添加定时增强清理器(由状态控制)。
Codex CLI
Codex 不会设置 CLAUDE_PROJECT_DIR,因此服务会回退到 Codex 启动时所在的工作目录:
codex mcp add cgraph \
-- /home/you/.local/lib/cgraph/bin-v0.9.7/cgraph-mcp \
--daemon /home/you/.local/lib/cgraph/bin-v0.9.7/graphd
……或者直接编辑 ~/.codex/config.toml(在 args 中添加 "--root", "/abs/path/to/your/project",即可固定项目,不受工作目录影响):
[mcp_servers.cgraph]
command = "/home/you/.local/lib/cgraph/bin-v0.9.7/cgraph-mcp"
args = ["--daemon", "/home/you/.local/lib/cgraph/bin-v0.9.7/graphd"]
重启 Codex,并在 TUI 中运行 /mcp 进行确认。
Cursor、Windsurf 和其他 MCP 客户端
任何能够启动 stdio 命令的 MCP 客户端都可以使用——添加包含命令和参数的服务条目(大多数客户端使用类似于 Claude Code 的 .mcp.json 的 mcpServers JSON 块)。对于不会设置 CLAUDE_PROJECT_DIR 的客户端,请显式设置 --root。手动对服务进行冒烟测试:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
| build/release/src/mcp/cgraph-mcp --root . \
--daemon build/release/src/daemon/graphd
能检测代码变更的检查点
graph_remember 会存储一个检查点(标题和简短摘要),并将其关联到它 touches 的符号。每个关联都会记录该符号自身源代码片段的 sha256。graph_recall 会重新计算该片段的哈希,并将每个关联报告为:
valid:自检查点建立以来,该符号的代码未变更。changed:该符号之后被编辑过,或其文件已被编辑但图尚未更新。请重新读取后再相信摘要。unanchored:无法进行检查(例如,没有源代码片段的模块节点)。
重命名或删除的符号会列在 gone 下。只要有任何内容发生变更或已消失,检查点本身就会标记为 stale;如果没有关联或某些关联无法检查,则标记为 unverified;否则标记为 valid。锚点涵盖的是代码片段,而不是整个文件:同一文件中的其他函数被编辑,或代码仅向下移动,都不会影响关联的 valid 状态。检查点保存在 cgraph-out/memory/ 中,并在守护进程重启后保留其锚点。
cgraph-client --root . update '{"path":"."}'
cgraph-client --root . remember '{"title":"Billing refactor","body":"next: wire refund into the API","touches":["charge","refund"]}'
# edit the body of refund(), then sync
cgraph-client --root . update '{"path":"."}'
cgraph-client --root . recall '{}'
{"label": "Billing refactor", "validity": "stale", "gone": [],
"concerns": [{"label": "charge", "validity": "valid"},
{"label": "refund", "validity": "changed"}]}
之后重命名 refund 会移除它的关联,并将其列在 gone 下。关联会跟随其建立时使用的符号名称,因此不会用 refund_all 替代它。
CLI、守护进程和 MCP 参考
⌨️ CLI · 🛰️ 守护进程与轻量客户端 · 🔌 MCP 服务内部机制
CLI
cgraph [--root PATH] [--out PATH]
cgraph enrich-plan [--root PATH] [--out PATH] [--drop DIR]
cgraph enrich-ingest [--root PATH] [--out PATH] [--drop DIR]
默认值:--root .、--out cgraph-out、--drop → CGraph 在输出路径下的语义数据存放目录。
# Build deterministic exports.
build/release/src/cli/cgraph --root /path/to/project --out /tmp/cgraph-out
# Create a semantic chunk plan for host enrichment.
build/release/src/cli/cgraph enrich-plan --root /path/to/project --out /tmp/cgraph-out
# Ingest host-written chunk_NN.json fragments and re-export the graph.
build/release/src/cli/cgraph enrich-ingest --root /path/to/project --out /tmp/cgraph-out
报告
cgraph report modules 会从常驻守护进程(不存在时会启动)绘制项目的模块依赖关系图:文件按模块分组,显示模块间带计数的 imports/CALLS,按最长依赖路径排列层级(第 0 层 = 没有模块依赖它),并列出所有循环依赖。除非指定 --include-tests,否则会排除测试根目录。
单仓库按其声明的包分组,而非按目录深度分组。程序会读取根目录的工作区清单(package.json 中的 workspaces、pnpm-workspace.yaml 中的 packages:、Cargo [workspace] 中的 members、go.work 中的 use),展开其成员 glob,并将每个声明了自身清单的匹配目录按其实际名称作为模块,因此图中会显示 @turing/web 和 ui-kit,而不是 apps 和 packages。不属于任何包的文件会保留其目录名称,不会丢弃任何内容;响应会通过 group_by、manifest 和 packages 说明它回答的问题。如果项目没有工作区清单,则与之前一样按目录深度分组。--group-by packages|depth 可覆盖此选择。分组在生成报告时计算:不会向 graph.json 添加 package 节点。
cgraph report modules --root /path/to/project --scope src # Mermaid `graph LR` on stdout
cgraph report modules --root /path/to/project --format json # modules / edges / layers / cycles
cgraph report modules --root /path/to/project --format svg > modules.svg
cgraph report modules --root /path/to/project --depth 1 --budget 2000
cgraph report modules --root /path/to/monorepo --group-by depth # ignore the workspace manifest
输出会按令牌预算限制大小(默认值为 6000,--budget 0 表示不限制):超出预算时,会删除权重较低的完整边或模块,并由 omitted 说明省略的数量。单次构建会在 graph.json 旁写入相同的示意图,文件名为 modules.mmd 和 modules.svg。
cgraph report types 审查同一守护进程中的类型定义:它会按名称比较提取出的 field 成员,范围包括 class/type 节点。它会列出 完全相同 的结构(成员声明完全一致、名称各异的类型组)、重复项(同一类型名称在多个文件中声明,并显示其成员集合的重叠程度)、重叠项(成员集合嵌套,且较小集合至少包含较大集合的一半,或相似度达到 --threshold;默认值为 0.80;只有成员数至少达到 --min-members 的类型才会参与,默认值为 3),以及 未引用类型(图中没有其他符号或文件引用它们;同一文件中的使用不构成边,因此请将其视为线索)。默认输出为 Markdown 表格,也可使用 --format json;行会按价值顺序逐步裁减,以符合预算。
cgraph report types --root /path/to/project --scope src # Markdown tables on stdout
cgraph report types --root /path/to/project --format json --threshold 0.6 --min-members 2
cgraph report clones 会查找复制粘贴的函数。提取时,每个函数体都会生成指纹:标识符转换为 ID,字面量转换为 LIT,注释会被去除,剩余内容按 5 个 token 为一组切片并哈希,再通过 winnowing 筛选。因此,仅名称或常量不同的两个副本会被视为相同;编辑过的副本则按保留内容的比例计分。指纹的 Jaccard 相似度至少达到 --threshold(默认值为 0.80)的函数会组成一个 克隆类,按规模从大到小列出,并包含每个成员的 file:line-line、最低成对相似度和最短函数体的 token 数。短于 --min-tokens 的函数体会被视为样板代码而跳过(默认值为 30)。仅由测试根目录中的函数组成的类会单独列出,除非指定 --include-tests。指纹不会写入 graph.json;守护进程会将其持久化到旁边的文件中,从较早的持久化数据快速加载图时会显示提示,直到下一次执行 update .。
cgraph report clones --root /path/to/project --scope src # Markdown tables on stdout
cgraph report clones --root /path/to/project --format json --threshold 0.7 --min-tokens 50
cgraph report design 会展示程序的入口结构。入口点包括 main、HTTP route 处理程序(内联的 app.get('/path', handler) 注册以及 Next.js app/**/route.ts 导出)、框架 page 文件(app/**/page.tsx、layout.tsx、pages/**),以及被图中其他位置均未调用、但自身有被调用函数的 root 函数;这些入口按 可达范围 排序,即每个入口传递调用的函数数量。每个入口都会显示最多 --hops 跳的顶层 调用流(默认值为 3),每个节点选取可达范围最大的四个子节点,其余子节点计入总数。报告最后会显示 层级(从入口到达各函数的最短调用距离,以及包含这些函数的模块)和无法从任何入口到达的函数数量。默认输出为 Markdown;使用 --format mermaid 可生成 flowchart TD,使用 --format json 可生成树形结构。单次构建会将其写入 design.mmd 和 design.md,取代旧的扁平 call-flow.html。
cgraph report design --root /path/to/project --scope src # entry table, flows, layers
cgraph report design --root /path/to/project --format mermaid --hops 2 # flowchart of the kept flows
这四种视图都通过 graph_report MCP 工具和守护进程的 report 操作提供。
守护进程与轻量客户端
build/release/src/daemon/graphd --root /path/to/project
守护进程运行期间会监视项目树:源代码编辑会在几秒内增量合并到图中(大型批次,例如切换分支,会合并为一次完整重新扫描),增量状态会在后台和关闭时重新持久化到 cgraph-out/。--no-watch 可禁用此功能。
守护进程的可选标志:
graphd --root PATH --idle-timeout SECONDS --no-watch
graphd --benchmark-query --graph PATH --query TEXT
graphd --version
使用轻量客户端(响应为 JSON;status 包含进程元数据、节点/边数量、缓存命中率和增强状态):
build/release/src/client/cgraph-client --root /path/to/project status
build/release/src/client/cgraph-client --root /path/to/project query '{"q":"Parser"}'
build/release/src/client/cgraph-client --root /path/to/project explain '{"id":"Parser"}'
build/release/src/client/cgraph-client --root /path/to/project path '{"source":"A","target":"B"}'
build/release/src/client/cgraph-client --root /path/to/project report '{"view":"modules","format":"mermaid","scope":"src"}'
build/release/src/client/cgraph-client --root /path/to/project update '{"path":"."}'
build/release/src/client/cgraph-client --root /path/to/project shutdown
MCP 服务
cgraph-mcp 通过 stdio 使用 MCP:它采用以换行符分隔的 JSON-RPC 2.0,实现 initialize、tools/list、tools/call 和 notifications/initialized(协议版本为 2024-11-05)。工具调用会通过轻量客户端使用的同一守护进程操作处理程序进行路由;无效 JSON 会收到 JSON-RPC 解析错误。有关注册方式和工具列表,请参阅与编码 Agent 配合使用。
宿主集成与增强
🧩 宿主钩子与常驻循环 · 🧠 语义增强片段
### 宿主集成CGraph 将提供方和模型相关逻辑置于原生二进制文件之外。宿主集成使用 cgraph-client 执行图操作,并通过各自的 Agent/模型工作流分发语义任务。参考钩子接受以下确定性守护进程操作:
integrations/hooks/cgraph-hook.sh status
integrations/hooks/cgraph-hook.sh query '{"q":"GraphSnapshot"}'
integrations/hooks/cgraph-pre-edit.sh 是 Claude Code 的 PreToolUse 钩子。在 Agent 编辑 Git 仓库中的文件之前,它会向 Agent 的上下文中添加最多 5 个最可能随所编辑行发生变化的文件,每行一个,并附上原因(src/routes.ts: 调用了 `record` 、
src/metrics_test.go: 此文件的测试);这些文件来自守护进程的 co-change 操作:调用或使用所编辑符号的文件(直接调用,然后再经过一个符号),按名称匹配的该文件测试文件或源文件对应项,以及图中与该文件相连的其他文件。如果父目录中的 cgraph.workspace.json 列出了该仓库,它还会指出位于该文件所服务或调用的端点另一侧的所有其他服务(… 服务 GET /api/v1/org/stats,由 turing-webapp lib/org-api.ts:83 (getOrgStats) 调用)。整条消息(包括两个部分)不超过 800 个令牌。无内容可说时不输出任何内容,并且绝不会阻止编辑;每次编辑最多检查文件的 8 个端点(其余端点会被列出)。它只会请求已经运行的守护进程(MCP 服务或 cgraph-client 会启动它们):它绝不会启动守护进程,也不会等待构建;超过整个钩子的单一期限 CGRAPH_HOOK_WAIT_MS(默认值为 250)后会静默放弃。仅导入路由文件的文件不会被视为拥有该文件中的任何路由。在 .claude/settings.json 中注册它:
{"hooks": {"PreToolUse": [{"matcher": "Edit|Write|MultiEdit",
"hooks": [{"type": "command", "command": "/path/to/CGraph/integrations/hooks/cgraph-pre-edit.sh"}]}]}}
Claude Code 插件已注册此钩子;如果你已安装该插件,请勿再将 cgraph-pre-edit.sh 添加到设置中,否则每次编辑都会运行两次查询。
同一查询可通过 cgraph-client --root REPO cross-service '{"file": "src/routes.ts"}' 执行。
实用环境变量:CGRAPH_CLIENT(客户端可执行文件)、CGRAPH_PROJECT_ROOT(项目根目录)、CGRAPH_DAEMON(守护进程路径)、CGRAPH_INTERVAL_SECONDS(常驻运行间隔,默认值为 30)、CGRAPH_REFRESH_ON_START(设为 0 可跳过初始更新)、CGRAPH_ONCE(设为 1 可运行一次状态检查后退出)。运行常驻循环参考实现:
CGRAPH_CLIENT=build/release/src/client/cgraph-client \
CGRAPH_PROJECT_ROOT=/path/to/project \
integrations/always-on/cgraph-always-on.sh
完整的宿主契约见 docs/host-skill-contract.md。
语义增强
由宿主驱动的工作流:(1)CGraph 为未缓存或已过期的语义输入生成分块计划;(2)宿主使用自己的模型/Agent 处理每个分块;(3)宿主将每个已完成分块恰好写入一个 chunk_NN.json 片段,并放入语义投放目录;(4)CGraph 在修改图之前验证每个片段;(5)有效片段会更新图和语义缓存,无效片段会被拒绝,且不会更改快照。
片段采用以下节点-链接结构(必需:节点 id/label;边 source/target/relation;超边 id/nodes/relation。可选:source_file、source_location、type/kind、confidence、confidence_score、properties、warnings):
{
"nodes": [{ "id": "doc:architecture", "label": "Architecture", "kind": "document" }],
"edges": [{ "source": "doc:architecture", "target": "component:engine", "relation": "describes" }],
"hyperedges": []
}
开发说明
- 在引擎中保持提取行为的确定性。特定提供方逻辑应放在宿主集成中。
- 在
tests/smoke/下添加引擎行为和集成接口的冒烟测试覆盖。 - 在
tests/fuzz/下为解析器或提取器强化添加模糊测试覆盖。 - 优先扩展中央语言配置和提取器流水线,而不是在使用方中添加临时提取逻辑。
- 交互式查看器特意设计为无依赖——生成的 HTML 中不包含 JS 库。
贡献
欢迎提交问题和 PR。使用 default 预设进行构建,确保 ctest --preset default 通过,并在迭代开发时优先使用 sanitizers 预设。
许可证
MIT。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。