calldiff 从 git 树直接读取调用关系,回答“谁调用谁”。它基于 tree-sitter 做纯语法分析,不做类型检查,因此速度快、无需构建,但会漏掉动态派发。输出应视为缩小关注范围的证据,而非“其他都不受影响”的证明。
已全局安装,直接运行 calldiff,不要用 npx calldiff。
三种常用模式:
calldiff reach --entry <fn> --to <symbol> --locs:影响范围。列出从入口点到目标符号的每条调用路径,每一跳带 file:line。适合在修改共享函数前使用,或回答“这次改动能否到达 X”。
calldiff diff --file <path>:对单个改动文件做结构化审查,显示该改动新增、删除或替换了哪些调用,并在树上下文中呈现。
calldiff tree --file <path>:输出某个模块的调用大纲,便于在阅读前了解其结构。
树过长时可加 --max-depth <n>。--locs 成本低且几乎总是值得加,行号能让输出可操作。
不要运行不带参数的 calldiff diff:它会在每个改动周围重新打印整棵未变化的调用树。在中等规模 React monorepo 上实测为 36k token,而原始 git diff 约 18k,成本翻倍且信息更少。同一测试中,用位置参数限定路径(diff HEAD <path>)静默返回空结果,不要依赖它。
用 --file 或 --entry 限定范围,一次一个目标。输出大小不确定时可先探测:calldiff diff --file <path> --token-count。
适用场景:修改共享辅助函数前,从应用入口点做 reach,几秒内得知有哪些调用者及位置,无需 Grep 扫描;审查时用改动文件列表(git diff --name-only)驱动 diff --file,而不是整个仓库。框架噪声确实存在:React 代码中每个 useState() 和 useRef() 都算一次调用,组件树大多是噪声。calldiff 对由具名函数构成的普通模块最有用,对组件树最没用。
局限:动态派发、高阶函数、依赖注入容器和 hook 间接调用无法解析。如果答案必须完整,calldiff 只是起点而非答案,报告结果时应说明这一点。还支持 --format json|md 等其他输出格式和 MCP 模式;CLI 文本输出是阅读和粘贴到审查中的合适默认值。