代码图谱子系统

packages/ttsc/internal/graph(~4.5k Go LOC)在一个 tsgo Program 上构建 checker 解析的代码引用图:符号是节点,类型解析的关系是边。packages/graph@ttsc/graph)把这张图通过 MCP 暴露给编码代理,让代理"读零个文件"就回答"什么调用什么、改动波及哪里"。

本子系统页面:

  • 构建与解析:节点收集、边解析、barrel re-export 解包、位置无关 ID。
  • MCP 服务器:typia 生成的工具表面、查询模型、3D 查看器。

核心理念:checker 解析而非语法猜测

graph 包的包注释(internal/graph/graph.goresolve.go:1)点出关键差异:因为图骑在 ttsc 的 in-process Checker 上,每条边都由真实类型检查器解析,而非语法启发。所以一条边能标"checker-resolved"而不是"guessed"。

README(packages/graph/README.md)对比 codegraph:codegraph 解析代码形状并推断连接,而 @ttsc/graph 问真实 TypeScript 编译器(它已解析过每个 import 与引用),所以图是精确而非推断的。在公共基准上,代理读零文件回答、token 降 77%-86%、工具调用降 94%-95%。

数据模型

Nodegraph.go:23)的关键设计是 位置无关 IDpath#name:kindnodeIDgraph.go:120)。从文件 realpath、声明名、kind 构建,所以在声明上方插一行不会re-key 它——这让未来的增量层不必每次编辑都churn 整张图(byte-offset key 会强制 churn)。

Edgegraph.go:83)的 Pos/End 是 evidence(产生边的源表达式范围)而非 identity;重复关系保留第一个源顺序 span。Origin 记录语法形式(value-call 的 call/new/jsx/tagged、heritage 的 extends/implements),让 JSON dump 把一种内部 kind 拆成更细的 schema kind 而 MCP 模型不丢区分。

barrel re-export:图谱的命门

resolve.go 持有图谱依赖的"承重原语":跟随一个引用到 checker 绑定的真实声明。最难的情形是 barrel re-export——pkg/index.ts re-export 兄弟的符号;这种形状承载了 monorepo 里几乎每条跨包边。

停在 GetSymbolAtLocation 会落到本地 import alias、在 index 文件处切断边,把输出塌回 tree-sitter 质量。解包 alias 链(Checker_getAliasedSymbol)落到真正声明符号的兄弟源(resolve.go:48)。Resolve 做这个解包,再分类声明住哪,让 node_modules / .d.ts 边界成为 external leaf 而非把依赖内部拉进图。

详见 构建与解析

CLI 与 MCP 两个消费者

  • cmd/ttscgraph:把图 dump 成 JSON(ttscgraph dump)。
  • @ttsc/graph:MCP 服务器(npx @ttsc/graph)+ 3D 查看器(npx @ttsc/graph view)。

@ttsc/graph 的 MCP 工具表面用 typia.llm.controller 从一个 TypeScript 接口反射生成 JSON schema 与参数校验器——没有手写 schema(见 MCP 服务器)。

设计取舍

取舍选择原因
边的来源checker 解析精确而非推断,区别于 codegraph
节点 ID位置无关 path#name:kind编辑不 re-key,利于未来增量
边去重from\x00to\x00kind 的 seen mapO(N) 构建而非 O(N²)
external 边界node_modules / .d.ts 作 leaf不把依赖内部拉进图
MCP schematypia 从 TS 接口反射无手写 schema,类型即契约

接下来