Hotdry.

Article

Git 实体级代码理解:Sem 的语义索引与跨文件查询方案

Sem 在 Git 之上构建实体级语义层,用 tree-sitter 提取函数/类/方法作为原子单元,实现跨文件影响分析与 Agent 友好的结构化 diff。

2026-06-07systems

传统代码审查工具依赖行级 diff 和 LSP 的符号索引,在 AI Agent 批量生成代码的场景下暴露出结构性缺陷:Agent 不关心第 43 行改了什么,它需要知道 " 函数 validateToken 被添加 "或" 类 AuthService 的依赖关系发生变化 "。Ataraxy Labs 开源的 Sem 正是针对这一断层设计的 —— 它在 Git 之上构建了一层实体级语义抽象,将代码解析为函数、类、方法等逻辑单元,实现真正意义上的语义化版本控制。

从行级到实体级:为什么需要新的 diff 原语

Git 的 diff 算法基于文本行的增删改,这在人类审阅时尚可接受,但在以下场景会产生信息损耗:

  • 格式化噪音:一次代码格式化会淹没真正的逻辑变更
  • 重命名盲区:函数改名后,Git 视为删除 + 新增,丢失连续性
  • 跨文件依赖盲区:修改某函数签名时,无法自动识别哪些调用点需要同步调整
  • Agent 理解成本:LLM 需要额外推理才能从行级变更映射到语义变更

LSP 虽然提供符号索引,但它是一个运行时服务,需要编辑器保持连接,且难以批量查询历史版本中的实体关系。Sem 的核心洞察是:将代码解析为 AST,提取实体作为一级对象,在 Git 工作流之上构建持久化的语义层

Sem 的架构:三层实体匹配策略

Sem 使用 tree-sitter 解析代码生成 AST,提取函数、类、方法、接口等实体,并通过三阶段匹配算法建立版本间的实体对应关系:

  1. 精确 ID 匹配:同名同路径的实体直接关联,标记为 modified 或 unchanged
  2. 结构哈希匹配:计算 AST 的结构哈希(忽略空白和注释),相同结构不同名称的实体视为 rename 或 move
  3. 模糊相似度匹配:当结构哈希不匹配时,计算 token 重叠率(>80% 视为 probable rename)

这种分层策略使得 Sem 能识别传统 diff 无法捕捉的变更类型:函数重命名、代码块移动、文件间迁移等。例如,当你将 validateToken 函数从 auth.ts 移动到 token.ts 并改名 verifyToken,Sem 会输出 "validateToken moved to token.ts and renamed to verifyToken",而非" 删除 15 行,新增 15 行 "。

核心命令与工程实践

Sem 提供六个核心命令,覆盖从日常 diff 到 Agent 集成的全场景:

语义化 Diff

# 查看工作区变更,输出实体级描述
sem diff

# JSON 格式供 CI/Agent 消费
sem diff --format json

# 查看某函数的历史演变
sem log authenticateUser --limit 20

JSON 输出包含结构化字段:entityId(如 src/auth.ts::function::validateToken)、changeType(added/modified/deleted/moved/renamed)、行列号等,Agent 可直接消费而无需解析文本。

跨文件影响分析

# 查看修改 authenticateUser 会波及哪些测试和依赖
sem impact authenticateUser --tests --dependents

该命令构建跨文件依赖图,识别直接依赖、反向依赖和受影响测试,在重构前提供精确的变更范围评估。

实体级 Blame 与上下文提取

# 查看某文件各实体的最后修改者
sem blame src/auth.ts

# 为 LLM 提取 token 受限的上下文(实体+依赖+被依赖)
sem context authenticateUser --budget 4000

sem context 特别适用于 Agent 场景:当 Agent 需要理解某函数时,自动提供其签名、依赖的实体、调用它的实体,且严格控制在指定 token 预算内。

语言支持与扩展配置

Sem 通过 tree-sitter 原生解析(非 WASM)支持 27 种编程语言,涵盖主流技术栈:

语言族 代表语言 提取实体
前端 TypeScript, JavaScript, Vue, Svelte functions, classes, interfaces, exports
系统 Rust, Go, C, C++, Zig functions, structs, traits, impls
JVM Java, Kotlin, Scala classes, methods, interfaces
脚本 Python, Ruby, PHP, Perl functions, classes, modules
数据 JSON, YAML, TOML, CSV properties, sections, rows

对于非标准扩展名,可在项目根目录创建 .semrc 映射:

.xyz = cpp
.j = json

无扩展名文件则通过内容启发式检测(shebang、import 语句、vim modeline)。

与 Git 生态的集成

Sem 采用 Git 兼容设计,不改变仓库结构,仅在 OS 缓存目录维护 SQLite 实体索引。支持两种深度集成模式:

模式一:Git 默认 diff 替换

sem install-git-diff  # 替换 git diff 输出

安装后,git diff 自动输出实体级描述,同时安装 pre-commit hook 展示暂存区的实体级影响范围。

模式二:MCP Server 供 Agent 调用

Sem 内置 MCP 服务器,暴露 6 个工具:sem_entitiessem_diffsem_blamesem_impactsem_logsem_context。Agent 通过标准 MCP 协议调用,无需关心 CLI 细节。

{
  "mcpServers": {
    "sem": { "command": "sem-mcp" }
  }
}

工具栈协同:Sem + Weave + Inspect

Sem 是 Ataraxy Labs 代码理解栈的底层原语,与另外两款工具形成闭环:

  • Weave:实体级 Git merge driver,用实体级三向合并替代行级合并。两个 Agent 在同一文件的不同函数工作?自动解决。同一函数冲突?输出语义化冲突描述而非行号冲突。
  • Inspect:实体级代码审查工具,基于依赖图对变更实体进行风险评分,API 删除自动置顶,独立变更分组审查。

三者共享 sem-core 库,确保实体定义和缓存的一致性。

局限与权衡

Sem 并非银弹,采用前需评估以下约束:

  1. 命名冲突:与 GNU Parallel 的 sem 命令冲突,需通过 alias 或 npx/bunx 调用
  2. 解析边界:依赖 tree-sitter 的语法覆盖率,对于宏生成代码、重度元编程场景可能提取不完整
  3. 缓存开销:大型仓库的初始索引需要一定时间,后续增量更新轻量

结语

Sem 代表了代码理解工具从 "文本中心" 向 "语义中心" 的演进。它不取代 Git,而是在其之上构建 Agent 友好的语义层 —— 将函数、类、方法作为一等公民,实现真正的跨文件实体查询和影响分析。对于运行多 Agent 的代码仓库,或需要批量重构、自动化审查的团队,Sem 提供了一个比 LSP 更轻量、比文本 diff 更精确的中间层方案。


参考来源

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com