mcpField
C
Code Crawler

Code Crawler

本地服务
nithin554.github.io开发工具02 天前更新

关于 Code Crawler

Code Crawler 将 Java、Python、TypeScript 和 JavaScript 项目索引为 SQLite 属性图,为编码 Agent 和 LLM 客户端提供代码调用关系查询。它将静态调用、动态分派及框架路由等关系纳入图中,并支持增量索引。

主要功能

  1. 01四种语言写入同一代码图
  2. 02查询调用者、被调用者和调用路径
  3. 03追踪动态分派与反射关系
  4. 04索引依赖注入和 HTTP 路由
  5. 05增量索引并缓存解析结果
  6. 06查找热点、死代码候选和符号

适用场景

  1. 01追踪某个方法的传递调用者
  2. 02评估更改方法的影响范围
  3. 03分析框架路由和依赖注入
  4. 04定位热点与潜在死代码

MCP 相关

它通过 stdio MCP 服务向客户端提供代码图工具,包括符号搜索、调用追踪、实现查找和热点分析。索引保存在 SQLite 数据库中。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"nithin554-code-crawler": {
"command": "npx",
"args": [
"-y",
"@nithin554/code-crawler"
],
"env": {
"CODECRAWLER_DB": "<CODECRAWLER_DB>",
"CODECRAWLER_EXCLUDE_DIRS": "<CODECRAWLER_EXCLUDE_DIRS>",
"CODECRAWLER_DEFAULT_LIMIT": "<CODECRAWLER_DEFAULT_LIMIT>",
"CODECRAWLER_MAX_DEPTH": "<CODECRAWLER_MAX_DEPTH>",
"CODECRAWLER_HELPFUL_ERRORS": "<CODECRAWLER_HELPFUL_ERRORS>"
}
}
}
}
启动命令npx -y @nithin554/code-crawler
需要配置的环境变量
CODECRAWLER_DBPath to the SQLite graph database (default ~/.code-crawler/index.db)
CODECRAWLER_EXCLUDE_DIRSComma-separated directories to skip during crawl
CODECRAWLER_DEFAULT_LIMITDefault result cap for list queries (default 50)
CODECRAWLER_MAX_DEPTHTraversal depth cap (default 10)
CODECRAWLER_HELPFUL_ERRORSFuzzy suggestions on unknown symbols (default true)

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@nithin554/code-crawler0.1.86

工具

27

收录的工具列表 · 更新于 1 天前

参数类型必填说明
pathstring是要编入索引的项目根目录的绝对路径。
forceboolean否重新解析每个发现的文件,忽略清单中的大小、mtime 和内容哈希快捷判断。当索引可能已过期但元数据匹配时使用——否则,同大小编辑并恢复 mtime(git checkout、rsync -t、unzip)会从缓存重放。
languagestring否将索引限制为一种解析器语言:'java'、'python'、'typescript' 或 'javascript'。扩展名不属于该语言的文件将被跳过。默认根据文件扩展名检测。
fileFilterstring否可选的子字符串筛选器:仅索引路径包含此字符串的文件。

详细介绍

Code-Crawler

npm version npm downloads npm total downloads

由代码库持久化属性图提供支持的静态与动态调用追踪 MCP 服务。

Code-Crawler 会将项目索引到由 SQLite 支持的属性图中,涵盖文件、包、模块、类、接口、方法、字段、导入、注解/装饰器、调用边、虚拟分派、切面通知,以及框架添加的连接关系(入口点、路由、注入的依赖项、反射查找),然后将该图作为 MCP 工具提供给大模型客户端:查找调用方、追踪调用路径、探索邻域、跟踪运行时分派,以及在整个代码库中搜索符号。

支持 Java、Python、TypeScript 和 JavaScript 项目,并可将它们全部索引到同一张图中。

📖 文档网站: https://nithin554.github.io/code-crawler/ 📘 任务指南: USAGE.md 🗺️ 路线图: ROADMAP.md

        ┌───────────────────────  MCP (stdio)  ───────────────────────┐
        │  crawl_project  search_symbols  get_callers  get_callees     │
        │  trace_path     trace_execution  find_aspects  get_decorators│
        │  find_implementations  find_hotspots  find_dead_code  ...    │
        └───────────────┬──────────────────────────────┬───────────────┘
                        │                              │
                 ┌──────▼───────┐             ┌────────▼────────┐
                 │   crawler    │             │  graph queries  │
                 │ parse+resolve│             │ recursive CTEs  │
                 └──────┬───────┘             └────────┬────────┘
                        │                              │
                 ┌──────▼──────────────────────────────▼──────┐
                 │        SQLite property graph (WAL)          │
                 │   nodes (kind, fqn, props) + edges (kind)   │
                 └─────────────────────────────────────────────┘

为什么需要图?

纯文本 grep 无法回答这类问题:

  • “谁会传递调用 OrderRepository#create?”
  • “从 Main#main 到 Tracking#dispatch 是否存在静态路径?”
  • “更改 OrderService#place 的影响范围有多大?”

这些问题需要遍历图。Code-Crawler 会构建一次图(按文件幂等构建),并通过有界递归 SQL 查询来回答。

功能

  • 四种语言,一个图谱:Java(tree-sitter-java)、Python (tree-sitter-python)、TypeScript(tree-sitter-typescript、tree-sitter-tsx)和 JavaScript(tree-sitter-javascript)——全部采用 WASM,无需原生构建步骤。 按文件检测语言,因此多语言代码库会汇入同一个图谱。
  • 在静态图谱上进行动态追踪——DYNAMIC_DISPATCH 边将接口/基类调用连接到项目中的每个重写实现;ADVISES 边则将 AspectJ/Spring AOP advice 编织到执行轨迹中(before、after、around、after-returning、 after-throwing、命名的 @Pointcut 引用、&&/||/! 以及 execution、within、 @annotation、@within、target、this、args 原语)。
  • 运行时间接调用有名可查,不会丢失——Java 反射/ServiceLoader、Python getattr/importlib 和 JS require/动态 import() 在目标可静态确定时,会生成指向解析后符号的 REFLECTS 边;否则会生成指向 __reflect__: 标记的边。
  • 框架装配——依赖注入(@Autowired、NestJS 构造函数注入、FastAPI Depends)会变为 INJECTS 边;HTTP 路由(@GetMapping、@RequestMapping、NestJS @Controller/@Get、FastAPI/Flask/Quart 装饰器、Express app.get)会变为指向处理程序的 ROUTES 边,处理程序也会被标记为入口点。
  • 调用边上的控制流——每条 CALLS 边都会记录调用是否有条件,以及其外层守卫/循环的精简源码文本,因此 Agent 无需读取文件,就能区分正常路径和错误路径。
  • 明确标记外部依赖——第三方导入和运行时全局项会成为包级别的 external_symbol 节点,并通过 REFERENCES 边关联;find_external_dependencies 会提供汇总结果,与项目代码明确区分。
  • 注解作为一等节点——@Transactional、@Cacheable、@Async、@Retryable、Python 装饰器和 TS 装饰器会成为 annotation 节点,并通过 ANNOTATED_WITH 边关联,因此可以查询类型、方法和字段上的装饰器。
  • 超越简单字符串匹配的调用解析启发式规则: 根据局部变量、参数、字段、new T() 表达式推断接收者类型;处理 self/this 作用域、点分路径(service.repo.find)、循环变量元素类型、不带限定符/this/super 调用、静态/限定调用以及超类链。 无法解析的调用会成为 unknown_symbol 幻影节点,并生成 resolved:false 边,确保图谱始终可遍历。
  • 增量式、按文件索引——每次扫描都会记录逐文件清单 (size、mtime、SHA-256),并对内容未变的文件重用缓存的解析结果,因此重新扫描时只会重新解析实际发生变化的文件。变更会通过 filesAdded/filesUpdated/filesUnchanged/filesRemoved 报告。只有发生变更时,才会重建派生动态边并清理孤立项。完整重新扫描仍会移除已消失的文件,且不会重复构建图谱;限定范围的扫描(fileFilter 或强制指定的 language)绝不会移除范围外的文件。如果检出操作、rsync -t、保留 mtime 的解压操作或时钟偏差导致编辑后的文件恢复为原来的大小和 mtime,此快捷判断就会漏掉变更——请传入 force(CLI 中使用 --force)以重新解析所有内容。
  • 由 FTS5 驱动的符号搜索——符号查找基于外部内容 FTS5 索引运行,并采用 BM25 排名和精确/前缀加权;仅当 FTS 未找到结果时才回退到 LIKE 扫描。随着图谱增长,搜索性能不会下降。
  • 带自动迁移的版本化架构——数据库会保存架构版本,并在打开时自动原位升级。如果数据库由更新版本构建程序写入,则会明确报错,而不会假定已知其结构并继续读取。
  • 每个项目一个数据库——索引位于 <project>/.code-crawler/index.db,因此两个代码库绝不会冲突。若将已填充的数据库指向其他项目,会提示使用 clear_index,而不会静默清除第一个索引。
  • 分析——热点(扇入/扇出/advice 数量)和死代码候选项。
  • 自包含:单个 SQLite 文件,无需外部服务。
  • 适合大模型:每个工具都会返回结构化 JSON;未知符号会附带模糊搜索建议。

安装和构建

需要 Node.js ≥ 22.12(better-sqlite3@13 和 Vitest 工具链都需要此版本)。

npm install
npm run build        # tsc → dist/
npm test             # vitest (full suite; set CODECRAWLER_BENCH=1 for the scale test)
npm run lint         # biome check (lint + format, fails CI)
npm run format       # biome format --write
npm run coverage     # vitest run --coverage (thresholds in vitest.config.ts)
npm run bench        # crawl/query benchmark on a generated ~137k-line project

命令行界面

codecrawler crawl <path>     # index a project
codecrawler crawl <path> --force  # re-parse every file, ignoring cached metadata
codecrawler status           # index summary
codecrawler query <symbol>   # fuzzy-lookup a symbol
codecrawler serve            # start the MCP server over stdio (default)

环境变量:

变量默认值用途
CODECRAWLER_DB<project>/.code-crawler/index.db数据库路径。向上从当前工作目录解析,因此每个项目都有自己的文件;如果当前工作目录不属于任何项目,则回退到 ~/.code-crawler/index.db。主目录仅用于回退,不会被视为上级项目
CODECRAWLER_EXCLUDE_DIRS.git,node_modules,dist,build,target,...爬取时跳过的目录
CODECRAWLER_DEFAULT_LIMIT50默认结果上限
CODECRAWLER_MAX_DEPTH10遍历深度上限(1–100)
CODECRAWLER_MAX_FILE_BYTES1000000大于此大小的文件会被跳过,不会解析
CODECRAWLER_RESPECT_GITIGNOREtrue发现文件时遵循项目的 .gitignore
CODECRAWLER_HELPFUL_ERRORStrue找不到符号时提供模糊匹配建议
CODECRAWLER_PLUGINunset插件模式:在调用时为每个项目解析一个数据库,而不是在启动时打开一个文件
CODECRAWLER_PLUGIN_ROOTunset插件安装目录,不会作为项目编入索引
CODECRAWLER_PLUGIN_DATAtemp dir无项目时回退数据库的可写目录

无效值不会导致服务崩溃:布尔值会回退到默认值,整数会限制在文档规定的范围内;每种情况都会向 stderr 输出一行警告。

索引内容

crawl 会遍历项目,并跳过 .git、node_modules、构建输出以及 CODECRAWLER_EXCLUDE_DIRS 中的其他条目。此外:

  • 会遵循项目的 .gitignore(设置 CODECRAWLER_RESPECT_GITIGNORE=false 可禁用),

  • 项目根目录中可选的 .crawlerrc 可以通过 gitignore 风格的 glob 缩小或扩大文件发现范围,并调整模块 ID 的起始位置:

    {
      "include": ["src/", "packages/core/"],
      "exclude": ["**/generated/"],
      "moduleRoots": ["src"]
    }
    

    当 include 非空时,只会将匹配的文件编入索引。格式错误的 .crawlerrc 会被忽略并发出警告,不会导致爬取失败。

    moduleRoots 默认不启用:设置为 ["src"] 后,src/orders/service.ts 会成为模块 orders.service,而不是 src.orders.service,这与使用 baseUrl 或 PYTHONPATH 的项目实际导入的模块一致。系统不会自动推断该设置,因为这会悄然改变现有索引中的编号。TypeScript 和 JavaScript 项目还会自动获得路径别名支持:系统会从 tsconfig.json(或 jsconfig.json)读取 compilerOptions.paths 和 baseUrl,并在项目内解析 extends,因此 import { x } from "@app/orders/service" 会解析到实际模块,而不是记录为外部模块。更改任一设置后,下次爬取时会重新解析缓存文件。

  • 超过 CODECRAWLER_MAX_FILE_BYTES 的文件以及疑似二进制文件会被跳过;crawl_project 会在 skippedFiles 中列出这些文件及其原因。

  • 单个文件的解析失败不会中止爬取;相关错误会记录在 parseErrors 中。

目前只读取根目录中的 .gitignore,尚不支持嵌套的 .gitignore 文件。

增量爬取

每次爬取都会为每个文件记录一行清单(size、mtime、SHA-256、语言)。在 下一次爬取时,如果文件大小和截断后的 mtime 都匹配,就会直接使用缓存的解析结果, 而不是重新读取和解析;如果文件的 mtime 发生变化但哈希值未变,也会从缓存中重放。 解析、虚拟节点创建和派生边仍会针对整个内存文件集运行,因此跨文件边仍然正确—— 只有 I/O、解析和数据库写入会跳过。

crawl_project 会报告以下统计:

字段含义
filesAdded本次爬取中首次发现的文件
filesUpdated内容已更改并重新解析的文件
filesUnchanged从解析缓存中重放的文件
filesRemoved已从磁盘消失并被清除的文件

**已知限制:**由于未更改文件的边不会重写,因此,如果某个调用仅因其他文件发生变化 而变得新近可解析(例如,某个方法新增了一个重载,或者引入了一个类,使之前有歧义的 调用现在可以绑定到该类),未触碰的文件中的该调用不会更新。触碰该文件,或运行 clear_index,即可强制执行完整解析。

基准测试

npm run bench 会生成一个合成的 TypeScript 项目(575 个模块、约 137k 行、 约 9.8k 个方法),对其进行爬取,并报告爬取各阶段的耗时和查询延迟百分位数。 tests/perf.test.ts 会断言相同的性能预算;该测试为可选测试,因为爬取真实规模的 代码库需要一些时间:

npm run bench                                        # human-readable report
CODECRAWLER_BENCH=1 npm test -- tests/perf.test.ts   # enforce the budgets
CODECRAWLER_BENCH_LINES=20000 npm run bench          # smaller corpus

以下数据是在磁盘缓存预热的 Windows 开发笔记本上测得;绝对数值会有所变化, 重要的是比例和索引的使用情况:

阶段耗时新增更新未更改
冷启动爬取22.5 s57500
重新爬取,未发生变化0.91 s00575
编辑一个文件后重新爬取0.75 s01574
查询p50p95预算(p95)
search_symbols(精确)0.69 ms1.96 ms50 ms
search_symbols(前缀)1.60 ms4.09 ms50 ms
get_callers(深度 2)0.16 ms0.21 ms50 ms
get_callees(深度 2)0.11 ms0.15 ms50 ms
get_subgraph(半径 2)0.16 ms0.27 ms50 ms
find_entry_points0.12 ms0.29 ms50 ms
find_hotspots145.5 ms181.4 ms1000 ms
find_dead_code99.8 ms290.3 ms1000 ms

对于上述 14.4k 个节点和 38.8k 条边,索引大小为 66 MB。find_hotspots 和 find_dead_code 会对整个项目中的每条边和每个节点进行聚合,因此它们的性能预算 比符号查找宽松一个数量级;这两个查询都使用 EXPLAIN QUERY PLAN 进行了性能分析, 并由 idx_edges_target_kind 提供服务。

CI 与发布

GitHub Actions 会在每个拉取请求上运行(pull-request.yml):npm ci → 类型检查 → 测试 → 构建 → 对构建后的 CLI 进行冒烟测试。

合并到 main 后会触发 publish.yml,该工作流会重新运行相同的检查,然后在 全部通过后自动发布:

  1. 读取最近的 v* 标签(首次运行时读取 package.json 版本), 计算下一个补丁版本,并推送 vX.Y.Z 标签。
  2. 创建对应的 GitHub 发布版本。
  3. 使用 provenance(OIDC 可信发布)将包发布到 npm 注册表——无需令牌或 仓库密钥。此步骤支持幂等执行和重试:跳过 npm 中已有的版本;透明日志或注册表暂时出现故障时,也不会再中止发布。
  4. 使用单独且可重新运行的作业,通过 ExaDev/mcp-registry-publish 将 server.json 以 io.github.nithin554/code-crawler 的名称发布到 官方 MCP 注册表。 该作业在 mcp-registry-publish 环境中运行,此环境的部署分支策略仅允许 main。 它会将 server.json 固定到发布版本,使用 GitHub OIDC 进行身份验证,跳过注册表中已有的版本,并在 npm 传播或注册表暂时出错时重试。所有权通过 package.json 中的 mcpName 字段验证。

用作 MCP 服务

安装为 Agent 插件

此仓库也是一个 Agent 插件,因此 Claude Code 和 Copilot CLI 可以一起安装服务和技能:

# Claude Code
/plugin marketplace add nithin554/code-crawler
/plugin install code-crawler@code-crawler

# Copilot CLI
copilot plugin marketplace add nithin554/code-crawler
copilot plugin install code-crawler@code-crawler

该插件运行 npx -y @nithin554/code-crawler serve。Agent Plugins 客户端会在插件目录中启动插件的 stdio 服务,因此服务不会将该目录视为你的检出目录:它会从 crawl_project、客户端的工作区根目录或此会话已选定的项目中解析项目,并将该项目的图谱保存在 <project>/.code-crawler/index.db 中——每个代码仓库一个数据库,布局与 CLI 使用的相同。如果你需要固定使用某个文件,仍可通过 CODECRAWLER_DB 指定。

同一技能也以 code-crawler 的名称发布在 ClawHub 上,插件也发布在那里:

  • 技能 — 使用 clawhub install code-crawler 安装。ClawHub 会在已发布版本的修订号上加 1 来分配此版本,因此每次只前进一个版本,而不会复制 npm 版本;如果某次发布的技能发布失败,两个版本号就会保持不一致,直到下一次发布。
  • 插件 — 作为 code-crawler 捆绑插件发布,版本号取自发布标签:发布 v0.1.67 会以 0.1.67 发布捆绑包。捆绑包中的 plugins/code-crawler/package.json 是打包元数据——ClawHub 需要包清单来检查捆绑包并构建其 ClawPack 产物,缺少清单时会发出警告——因此发布工作流会将标签中的版本写入其中、打包捆绑包并发布该 tarball;已提交的 version 只是占位符。

插件发布会在 Node 下直接运行官方 clawhub CLI,而不会调用上游可复用的 package-publish.yml。该工作流无法发布此捆绑包:它会使用 bun ... package validate 检查打包后的产物,而 CLI 拒绝在 Bun 下运行(“Plugin validation requires Node.js 22.13+ (22.x) or 24+”)。ClawPack 始终包含 package/package.json,且没有输入项会跳过此步骤,因此只有在捆绑包没有清单时,这个调用才曾成功——而这正是 ClawHub 会警告的问题。

发布仅限于 main。发布作业通过 clawhub-publish 环境进行部署,该环境的部署分支策略会拒绝任何其他引用,因此从功能分支手动触发时,会在上传前停止。CLAWHUB_TOKEN 是仓库机密,而不是环境机密:技能调用会将其传递给上游,上游会将其读取为 clawhub_token,而调用可复用工作流的作业无法声明环境。通过作业输出转发该值也没有用——GitHub 会拒绝输出机密值,并记录 Skip output 'clawhub_token' since it may contain secret。

一条命令

add-mcp 会将服务注册到你指定的客户端中——Claude Code、Cursor、VS Code、Claude Desktop、Codex、OpenCode、Zed 等(npx add-mcp list-agents)。请在要建立索引的仓库中运行:

npx add-mcp @nithin554/code-crawler --args serve -a cursor

这会在代码旁写入项目级配置(.mcp.json、.cursor/mcp.json、.vscode/mcp.json 等),因此每个仓库都有自己的注册配置和 .code-crawler/index.db。服务名称 code-crawler 根据包名生成。

--env CODECRAWLER_DB 是可选的——只有想将数据库放到其他位置时才添加:--env CODECRAWLER_DB=/absolute/path/to/index.db。-g 会改写入用户级配置。只有在客户端以你的检出目录作为工作目录启动服务时,才建议使用此选项:如果使用 -g 且工作目录与检出目录无关(Claude Desktop 会从其自身的安装目录启动),服务就会回退到 ~/.code-crawler/index.db,导致每个仓库都争用同一个文件。

每个项目使用一个服务

在插件模式以外,服务进程会在启动时打开一个数据库,而一个数据库只保存一个项目,因此模型是每个代码仓库注册一次——这正是上述每项目配置的作用。插件安装则会在调用时按项目解析数据库,因此一个插件即可服务于你打开的所有代码仓库。重新爬取代码仓库会复用其现有文件,只重新解析有更改的内容; 两个代码仓库绝不会共用一个文件,因此不会发生冲突。一次只会打开少量项目数据库(空闲的数据库会关闭),客户端断开连接时会释放所有句柄。

MCP Registry / npx

该服务已在 MCP Registry 中登记,名称为 io.github.nithin554/code-crawler。 任何支持 npx 的客户端都可以运行它,无需本地检出代码:

{
  "mcpServers": {
    "code-crawler": {
      "command": "npx",
      "args": ["-y", "@nithin554/code-crawler", "serve"]
    }
  }
}
Claude Desktop

claude_desktop_config.json:

{
  "mcpServers": {
    "code-crawler": {
      "command": "node",
      "args": ["/absolute/path/to/code-crawler/dist/index.js", "serve"],
      "env": { "CODECRAWLER_DB": "/absolute/path/to/your/index.db" }
    }
  }
}
Cursor / 其他 stdio MCP 客户端

将客户端指向同一命令。LLM Agent 的典型工作流:

  1. 对代码仓库运行一次 crawl_project。
  2. 使用 find_entry_points 查看执行实际从何处开始(路由、监听器、main 函数)。
  3. 使用 search_symbols 定位感兴趣的符号。
  4. 使用 get_callers / get_callees / trace_path 分析调用图。
  5. 使用 trace_execution 跟踪运行时分发和切面增强;使用 find_aspects、 get_decorators 和 find_annotated 检查会改变行为的注解。
  6. 使用 find_routes、find_injections、find_reflections 和 find_external_dependencies 梳理框架接线和第三方接口。

完整的、按任务组织的指南请参阅 USAGE.md。

MCP 功能

工具

工具用途
crawl_project为目录建立索引(幂等;每个数据库对应一个项目)
index_status节点/边/文件数量及上次爬取时间
search_symbols按名称或 FQN 模糊查找,可选按类型筛选
get_node获取单个符号的元数据和源代码位置
get_callers获取直接或传递调用者(限制深度)
get_callees获取直接或传递被调用者(限制深度)
trace_path两个符号之间的静态调用路径(BFS,优先返回最短路径)
trace_execution动态追踪:静态调用 + 虚拟派发 + 编织的增强逻辑
find_implementations查找接口或超类的实现类/子类
find_aspects查找切面、增强类型、切入点和被拦截的方法
get_decorators获取符号及可选成员上的注解/装饰器
find_annotated查找带有指定注解的所有符号
find_hotspots按扇入、扇出和增强拦截次数对方法排名
find_dead_code查找没有传入调用/派发/增强边的方法
find_entry_points查找由框架调用的根节点:HTTP 路由、监听器、调度器、CLI 主函数、测试
find_routesHTTP 路由 → 处理程序映射(@GetMapping、NestJS、FastAPI、Express)
find_injections查找 DI 提供的依赖(@Autowired、NestJS、FastAPI Depends)
find_reflections查找运行时查询:Class.forName、getattr、importlib、动态 import()
find_external_dependencies查找第三方包及其引用数量和文件数量
find_unresolved查找解析器无法绑定的调用点,并按失败原因分组
impact_analysis一次调用获取调用者 + 入口点 + 路由 + 重写项 + 风险等级
explain_symbol一次性摘要:成员、装饰器、调用者、被调用者、连接关系
find_cycles查找调用环(SCC),附带示例闭合路径,以及直接递归
export_graph将邻域渲染为 Mermaid 或 Graphviz DOT
get_subgraph提取用于可视化的邻域
get_schema描述图模型
clear_index重置数据库

资源:codecrawler://schema、codecrawler://status 提示词:trace-call-path(from, to)

令牌预算。 18 个返回列表的查询工具——即每个负载为单个结果列表的工具——均接受 max_tokens(100–200000)和 cursor。传入其中任一参数都会添加一个包含 totalCount、returnedCount、truncated、estimatedTokens 和 nextCursor 的 page 块;两者都省略时,响应格式保持不变,因此现有客户端仍可正常工作。只要还有项目,分页就不会返回空页,因此续页循环一定会终止。令牌数根据 JSON 长度估算(约每个令牌 4 个字符),无需依赖分词器,并且在不同模型间保持确定性。

get_subgraph 是唯一的例外:它的 nodes 和 edges 是同一切片的两个视图,因此截断其中一个列表会导致边指向已省略的节点。该工具改用 radius 和 limit 限制结果。聚合工具(impact_analysis、explain_symbol、export_graph)和单对象工具(get_node、index_status、get_schema)均返回单个对象,不接受预算。

Agent 技能

服务会告诉 Agent 它可以调用什么;技能会告诉它何时调用。skills/code-crawler/ 随 npm 包一同发布,内容为纯 Markdown——SKILL.md 用于发现技能和了解优先爬取的工作流,reference/tools.md 将全部 27 个工具映射到它们能解答的问题,reference/edge-semantics.md 则说明 confidence、external 和虚影节点。

npx skills add nithin554/code-crawler

skills CLI 会按约定找到它,并安装到你选择的任意 Agent;npx skills update 可刷新技能。若要手动安装,请将该目录复制到客户端的技能文件夹(~/.claude/skills/code-crawler/、.claude/skills/code-crawler/ 或插件的 skills/ 根目录)。无需构建步骤。请参阅 USAGE.md。

tests/skill.test.ts 会将技能的工具表与实时注册表进行断言校验,因此添加工具时表格不会过时。

图模型

节点——kind 的取值包括 file | package | module | class | interface | enum | method | constructor | field | import | external_symbol | aspect | annotation_type | annotation | unknown_symbol。每个节点都有全局唯一的 qualifiedName 和一个 JSON properties 数据块(签名、修饰符、行号、注解等)。

边——kind 的取值包括 CALLS | DECLARED_IN | CONTAINS | IMPLEMENTS | EXTENDS | IMPORTS | REFERENCES | DYNAMIC_DISPATCH | ADVISES | ANNOTATED_WITH | REFLECTS | INJECTS | ROUTES。CALLS 边包含 line、resolved、confidence,若调用位于控制流之后,还会包含 conditional、controlFlow 和 guards;ANNOTATED_WITH 边包含 line 和 arguments;ADVISES 边包含 adviceKind 和已解析的 pointcut;INJECTS 包含 injectedName 和 framework;REFLECTS 包含 mechanism;REFERENCES 包含 external。

ID 方案:

符号示例
类型com.acme.OrderService
方法com.acme.OrderService#place
构造函数com.acme.OrderService#<init>
字段com.acme.OrderService$repo
注解@Transactional
导入__import__:java.util.List
未解析的调用__unknown__:System.out.println
Python/TS 模块module:app.service
模块级函数module:app.api#read_order
模块级字段module:app.service$AUDIT_LOG
内联箭头处理函数module:src.routes#<anonymous@7>
外部包ext:fastapi
反射标记__reflect__:./plugins/${name}
注入标记__inject__:Repo

动态行为

静态调用边回答的是*“源代码字面上调用了什么?”*运行时行为有两处不同之处,两者都会建模为派生边,并在每次爬取时重新生成:

虚拟分派

对接口或基类方法的调用可能会执行任意一个重写方法。所有文件完成索引后,每条目标为可重写方法且已解析的 CALLS 边都会获得指向项目中找到的各个重写方法的 DYNAMIC_DISPATCH 边:

Checkout#checkout --CALLS--> Payment#pay
Payment#pay --DYNAMIC_DISPATCH--> CardPayment#pay
Payment#pay --DYNAMIC_DISPATCH--> UpiPayment#pay

构造函数以及 private/static/final 方法不在此列,因为它们不能被重写。使用 find_implementations 列出候选项,并使用 trace_execution 跟踪调用链。

切面、通知和装饰器

@Aspect 类型会被索引为 aspect 节点。通过 @Before、@After、@Around、@AfterReturning、@AfterThrowing 和 @Pointcut 识别通知方法。命名切入点(@Around("paymentCall()"))会解析为其 @Pointcut 表达式,然后与项目中的每个方法匹配,以创建 ADVISES 边:

AuditAspect#auditPayment --ADVISES--> Payment#pay
AuditAspect#auditPayment --ADVISES--> CardPayment#pay
AuditAspect#auditPayment --ADVISES--> UpiPayment#pay
AuditAspect#beforeCheckout --ADVISES--> Checkout#checkout

trace_execution 会将这些内容织入为 INTERCEPT 跳转,因此追踪结果如下:

Checkout#checkout
  INTERCEPT  AuditAspect#beforeCheckout
  CALLS      Payment#pay
    INTERCEPT        AuditAspect#auditPayment
    DYNAMIC_DISPATCH CardPayment#pay -> authorize
    DYNAMIC_DISPATCH UpiPayment#pay   -> settle

所有其他注解都会被索引为带有 ANNOTATED_WITH 边的 annotation 节点。框架装饰器(@Transactional、@Cacheable、@Async、@Retryable、@PreAuthorize、@Scheduled 等)还会标记为 decorator: true,以便 get_decorators 和 find_annotated 找到具有运行时语义的注解。

Python 装饰器和 TypeScript 装饰器采用相同的建模方式:@router.get(...)、@injectable、@Controller(...)、@Get(':id') 等会作为 annotations 写入声明它们的类型或方法;框架装饰器还会额外生成路由、注入或入口点标记。

反射和运行时间接调用

编译器无法解析的查找会记录为 REFLECTS 边。当目标可以静态确定时(字符串字面量、字面量前缀加变量(importlib.import_module("app.plugins." + name))、getattr(obj, "run")、require("./x")),边会指向解析出的符号。否则,它会指向一个携带 mechanism 的 __reflect__: 标记:

module:app.dynamic#load_plugin --REFLECTS--> __reflect__:app.plugins.   (importlib.import_module)
module:src.dynamic#loadPlugin   --REFLECTS--> __reflect__:./plugins/${name}   (import())

属于项目自身命名空间的动态模块前缀仍留在项目内;只有确实属于外部的包才会成为外部依赖。

依赖注入

由框架提供的依赖会表示为 INJECTS 边,而不是缺失的调用(find_injections):

框架检测依据
Spring@Autowired 字段、方法和构造函数
NestJS带装饰器的构造函数参数;@Injectable() 类
FastAPIDepends(...) 参数
dependency-injector@injectable 类的构造函数参数
plain TS/JS类型为项目类的构造函数参数

原始类型和结构化参数类型(string、{ id: string }、list[str])会被统计,但不会生成边,因此 @Body() body: { id: string } 不会污染图。

框架入口点和路由

框架调用的方法没有源代码层面的调用方,因此标记这些方法可以让死代码分析更准确,并为 trace_execution 提供根节点(find_entry_points、find_routes):

语言可识别项
Java@GetMapping、@PostMapping、@RequestMapping、@Scheduled、@EventListener、监听器注解、测试、main
PythonFastAPI(@router.get、@app.post、@router.websocket)、Flask(@bp.route、@app.get)、Quart(@bp.route)、if __name__ == "__main__"、pytest 测试
TypeScriptNestJS @Controller + @Get/@Post/...,Express app.get('/x', handler),Jest 测试

内联编写的 Express 处理函数会获得一个合成方法节点(module:src.routes#<anonymous@7>),因此其函数体仍会获得调用边、包含关系和路由边。

调用边上的控制流

每条 CALLS 边都会记录它是否受条件保护:

{
  "kind": "CALLS",
  "source": "app.repository.OrderRepository#create",
  "target": "ext:builtins",
  "properties": { "line": 14, "conditional": true, "controlFlow": ["branch", "loop"],
                  "guards": ["row.get(\"id\") == order_id", "self.rows"] }
}

controlFlow 使用 branch | loop | exception | predicate。保护条件是源代码文本,而非符号约束:它会告诉 Agent 某次调用是有条件的,以及条件的形式,但不会假装能够建模其真值。

解析启发规则

Java:

  1. 构造函数调用(new Foo(...))→ 解析 Foo → #<init>(若未显式定义,则使用默认构造函数)。
  2. 裸调用 / this. 调用 / super. 调用 → 所属类型及其父类链。
  3. 类型限定调用(com.acme.Foo.bar)→ 直接解析该类型。
  4. 实例调用(x.method())→ 从局部变量 → 参数 → 字段推断接收者类型, 然后搜索该类型及其祖先类型。
  5. 否则 → 创建虚设的 unknown_symbol 节点,设为 resolved:false,并将推断 失败原因附加到边上。

Python 和 TypeScript 在同一流程基础上增加了模块感知解析:

  1. 感知导入的作用域 — from app.repository import OrderRepository 会将本地 名称映射到 app.repository.OrderRepository;相对模块和项目模块按后缀解析,裸说明符则回退到包根目录。
  2. self.x / this.x 会根据所属类的字段解析;以 self/this/cls/super 开头的接收者永远不会被视为外部包。
  3. 点分接收者(service.repo.find)会逐段遍历:第一段解析为类型,下一段解析为该类型上的字段;list.append 等容器成员会被识别为内置成员,而非未知成员。
  4. 局部变量会根据参数、带注解的赋值、new T() 以及推断出的循环/变量元素类型确定类型(for row in self.rows: → row: dict)。
  5. 模块函数(module:app.api#read_order)会像类方法一样参与分派、路由和入口点标记。

每条已解析的边都包含一个 confidence 置信度分数和一条易读的 reason 溯源说明 (例如 via local orders: OrderService),以便 LLM 客户端评估应在多大程度上信任该结果。

已实现的语言

语言扩展名提取内容动态特性
Java.java包、类、接口、枚举、注解、构造函数、方法、字段虚拟分派、AspectJ/Spring AOP 通知、@Autowired、@RequestMapping 系列、Class.forName、ServiceLoader
Python.py, .pyi模块、类、函数、方法、字段、装饰器@injectable/Depends 注入、FastAPI/Flask/Quart 路由、getattr、importlib、__main__、测试
TypeScript.ts, .tsx, .mts, .cts模块、类、接口、类型别名、枚举、方法、字段、箭头函数NestJS/Angular 装饰器、DI 构造函数注入、NestJS/Express 路由、import()、require()
JavaScript.js, .jsx, .mjs, .cjs模块、类、函数、字段装饰器、Express 路由、require()

爬取目录时会自动混合处理所有这些语言——根据文件扩展名为每个文件选择语言,单次爬取即可返回 languages: ["java", "python", "typescript"]。

添加一种语言需要:

  1. src/parser/languages/<lang>.ts — 一个实现 parseFile 的 LanguageDescriptor(提取模块/包、类型/成员、原始调用点、本地类型表),以及三个声明式字段:resolver(处理该语言的调用解析器)、frameworks(其解析器可识别的框架)和 fixture(其一致性测试目录)。
  2. 在 src/parser/languages/index.ts 中添加一行。
  3. 在 tests/language-conformance.test.ts 的 CONTRACTS 表中添加一行——随后测试套件会要求新语言满足与其他所有语言相同的图不变量。
  4. 可选:在 src/resolver/ 中添加解析器,并通过 src/resolver/registry.ts 暴露;大多数语言可使用共享的 module 解析器,只需覆盖语言特有的内置项。
  5. 添加尚未注册且应识别的任何框架——在 src/parser/frameworks/ 中创建模块,声明其入口点、注入和标记。这只是数据变更:无需修改解析器。

框架按文件解析:首先依据导入证据,然后按描述符的 frameworks 顺序,最后尝试所有已注册模块。导入 dependency_injector 的文件会将其 @inject 属性归属于该框架;没有此类导入的 Java 文件也会回退到注册表,而不会丢失注解。解析器只保留真正属于语法的信息:调用式路由的接收者名称、路径拼接,以及记录为入口点详情的文本。

无需编辑任何现有语言模块:src/crawler.ts 中的分派会读取描述符的 resolver,因此语言不会出现在条件判断中。

所有语言使用相同的图架构、存储、遍历查询和 MCP 接口。候选后续语言及其所需的建模工作已在 ROADMAP.md 的第 3 阶段中排序。

未建模的内容

除下文列出的共通项外,各语言特有的缺口:

语言未建模的内容
javarecord 会被索引为带有 record: true 的 class;其组件会成为 private final 字段,并各自生成一个访问器,但在调用解析到隐式规范构造函数之前,该构造函数不会获得签名。Lambda 函数体会被索引,但像 this::handle 这样的函数引用没有静态目标,因此不会生成调用边。不会提取 static {} 和实例初始化块。泛型类型参数会被擦除,因此通过 List<T> 字段进行的调用会绑定到声明的上界,而不是具体参数。
python不建模元类、描述符、__getattr__ 或 __getattribute__。装饰器生成而非显式编写的成员(如 @dataclass 生成的 __init__ 和 __eq__,以及元类添加的任何成员)没有可指向的声明,因此对这些成员的调用仍无法解析。不会跟踪导入时的猴子补丁。存储在属性中并稍后调用的 lambda 没有目标。
typescriptnamespace X {} 代码块不会生成模块节点,因此其中声明的成员无法按名称访问。JSX 可以正常解析,但不会对元素树建模。类型级构造(条件类型、映射类型、模板字面量类型、交叉类型)不会进行求值,因此此类类型的接收者会回退为无法解析。不会提取 static {} 代码块和 accessor 字段。
javascript由 TypeScript 解析器处理,因此上述所有缺口均适用。由于没有注解,接收者只能来自 new、赋值的初始化表达式以及语言无关的启发式规则,因此动态类型代码的解析率低于带注解的代码。

按设计尚未建模的内容:

  • 值流和分支可行性——边会说明调用是否有条件,并记录守卫文本,但不会说明实际执行哪个分支。
  • 跨仓库解析——爬取目录之外的库会被概括为 ext: 包,而不会被爬取。
  • 完全动态的目标(getattr(obj, name)、require(userInput))会指向 __reflect__: 标记,而非符号;没有可供静态指向的目标。
  • 从配置文件而非代码组装的运行时 DI 图。
  • 从表达式而非名称推断接收者——getService().run()、items[0].run()、三元表达式或类型转换等调用会报告为无法解析。
  • 重载选择会优先使用参数数量,然后在调用位置明确给出参数类型时(字面量、new 表达式、类型转换)使用各参数的类型。如果调用无法区分两个重载(例如 f(count) 对应 f(int) 和 f(String)),仍会绑定到源码中第一个声明。重载声明在第一个之后会保留不同的 ID(Type#f、Type#f(int)、Type#<init>(String));ID 方案见 USAGE.md。

完整列表以及针对各项缺口所建模的内容,见 USAGE.md。

架构

src/
├── index.ts                    CLI (crawl | status | query | serve)
├── server.ts                   MCP server: tools, resources, prompt (thin handlers)
├── crawler.ts                  file discovery → parse → index → resolve → persist
├── config.ts                   env-overridable configuration
├── graph/
│   ├── schema.ts               node/edge kinds, id scheme, property shapes
│   ├── store.ts                better-sqlite3 schema + idempotent file-scoped upserts
│   └── queries.ts              recursive-CTE traversal (callers/callees/path/subgraph)
├── parser/
│   ├── language.ts             LanguageConfig / LanguageDescriptor contracts
│   ├── treeSitter.ts           WASM runtime + grammar loading + CST walk helpers
│   ├── controlFlow.ts          enclosing branch/loop/try/catch + guard text
│   ├── moduleId.ts             import-aware module id derivation
│   ├── methodId.ts             method id scheme (overload signatures, constructor ids)
│   ├── frameworks/
│   │   ├── types.ts            FrameworkModule / FrameworkScope contracts
│   │   ├── index.ts            registry + scope/entry-point/injection lookups
│   │   └── <framework>.ts      one module per framework (spring, nestjs, fastapi, …)
│   └── languages/
│       ├── index.ts            descriptor table + per-file language detection
│       ├── java.ts             Java structural extraction
│       ├── python.ts           Python structural extraction
│       └── typescript.ts       TypeScript, TSX and JavaScript extraction
└── resolver/
    ├── types.ts                TypeIndex (global name/method index) + Resolver contract
    ├── registry.ts             resolver lookup, capability query, default fallback
    ├── common.ts               builtin types, external packages, receiver inference
    ├── dynamic.ts              virtual dispatch + AspectJ pointcut matching/advice edges
    ├── genericResolver.ts      the `module` resolver (imports, barrels, overloads)
    └── javaResolver.ts         the `java` resolver (declared types, hierarchy, overloads)

路线图

语言无关的架构、更多语言支持以及更完善的解析均在 ROADMAP.md 中跟踪。近期重点是将新增一种语言所需的改动缩减为一个模块,然后以此为基础添加 Go、C# 和 Kotlin 支持。

文档站点

docs/ 中的静态文档站点由 .github/workflows/pages.yml 部署到 GitHub Pages。站点涵盖快速入门、MCP 工具参考和实例教程。可在本地打开 docs/index.html,或访问 https://nithin554.github.io/code-crawler/。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。