Skip to main content
Glama

MAST — Monorepo AST 搜索工具

MAST 是一个代码搜索引擎,既可以作为 MCP 服务器(供 AI 助手使用)运行,也可以作为独立 CLI 运行。它使用真正的 AST 解析器(tree-sitter)解析 TypeScript 和 JavaScript 源文件,将生成的符号图和代码分块存储在 SQLite 中,并通过 Reciprocal Rank Fusion 将词法 BM25 搜索与声明精确排序器融合来回答查询。

核心设计原则:只返回助手恰好需要的代码,不多不少。MAST 不会读取整个文件,而是返回与查询匹配的特定函数、接口或类型声明——从而节省 token、减少上下文噪音,让 AI 工具在大型代码库中导航时不会被无关内容淹没。


目录


Related MCP server: vs-token-safer

为什么选择 MAST?

当 AI 助手需要理解代码时,最直接的做法是读取整个文件。这会浪费 token(一个 200 行的文件中大部分内容与问题无关),膨胀上下文窗口,并迫使模型在每次调用时从噪音中筛选信号。

MAST 采用了不同的方法:

  • AST 级分块——每个函数、类、接口和类型别名都是独立的分块。助手获得它需要的精确声明,而不是它恰好所在的文件。

  • 排序搜索——BM25(FTS5)处理关键字和标识符查询;声明精确排序器(“ranker D”)捕获 BM25 的 trigram 分词器可能排序不一致的精确符号名查询。两者通过 Reciprocal Rank Fusion 融合,因此两个排序器都认可的分块会排在仅其中一个找到的分块之前。

  • 结构化查询——“谁调用了这个函数?”、“什么实现了这个接口?”、“这个文件导入了什么?”都通过预先构建的符号图来回答,而不是通过 grep 源码。答案即时且结构正确。

  • JIT 过期检测——每次读取时,MAST 都会检查磁盘上的文件自上次索引以来是否已更改。如果已更改,文件会在返回结果之前在后台透明地重新解析。索引绝不会在助手不知情的情况下过期。

  • Token 核算——每个工具响应都包含 _stats,其中包含返回的 token 数量以及反事实“一次简单的整文件读取会花费多少?”,从而为效率提供具体的度量。


环境要求

  • Node.js ≥ 22(本仓库在 .nvmrc 中固定了其开发所针对的版本)

  • C++ 工具链,用于两个原生模块(better-sqlite3、tree-sitter)。 预编译二进制文件覆盖大多数平台;当没有与你的 Node ABI 匹配的版本时,node-gyp 会从源码构建,并且需要:

    • macOS — xcode-select --install

    • Debian/Ubuntu — sudo apt install build-essential python3

    • Windows — 安装 Visual Studio Build Tools 中的“使用 C++ 的桌面开发”工作负载

查询时无需服务、无需 API 密钥、无需网络。一切都在本地 SQLite 中。


安装

作为你想要索引的项目的开发依赖——推荐这样做,因为这样版本就会与所有其他依赖一起固定在你的 lockfile 中:

pnpm add -D @spikedpunch/mast     # or: npm i -D / yarn add -D

或者全局安装,如果你希望在许多 checkout 之间共用一个 mast:

pnpm add -g @spikedpunch/mast

验证:

mast --version

快速开始

从零到可搜索索引只需三个命令:

cd /path/to/your/project

mast init                    # write .mast/, then run the first full index
mast status                  # confirm it is fresh
mast search "createUser"     # search it

mast search 打印匹配的声明,而不是它所在的文件:

$ mast search "compareVersions" -n 1
src/cli/upgrade-cmd.ts:39  compareVersions  function  (exported)
    /** Semver compare, prerelease-aware. Returns <0, 0, or >0. */
    export function compareVersions(a: string, b: string): number {
      ...
    }

270 tokens returned vs 2140 to read the files whole — 87% saved

最后一行是真实的核算,而不是口号:每个响应都携带 _stats,包含它返回的内容以及完整读取所引用文件的上限。对于小文件,节省可能是负的,MAST 会如实说明,而不是将其四舍五入为收益。

MAST 无法完全保证的答案会在显示结果的同一界面上如实说明。自索引以来被编辑过的文件会被标记,因为其下方打印的内容是旧的:

! 1 of 2 results are from files that changed since indexing —
  the code shown below may be out of date. Run `mast index` to refresh.

src/a.ts:1  alphaFunction  function  (exported)  [STALE]

空答案会区分它可能为空的两种原因:

$ mast search "kept_symbol"
no matches (mast indexes TypeScript, JavaScript, and Markdown only —
a symbol in any other language is invisible to it, not absent from the repo)

$ mast search "anything"          # in a directory with no index
nothing is indexed at this path — this is not evidence the symbol is absent.
run `mast index` first, or check `mast status` for the path being used.

使用 --type、--language、--exported、--file、-n 缩小范围:

mast search "greet" --type method --exported -n 5
mast search "config" --file "src/store/**"

在工作时保持最新——或者让 git 钩子来做:

mast index --incremental     # reindex only what changed
mast install-hooks           # reindex automatically after commits and checkouts

随构建发布的所有内容都可以离线阅读,因此你永远不必弄清楚哪些文档与你的版本匹配:

mast docs                    # list the topics
mast docs spec               # the full behavioural specification
mast skill                   # the instructions to paste into an agent prompt

从你的 AI 助手中使用

MAST 通过 stdio 使用 MCP 通信。mast serve 是服务器命令;下面的配置仅因每个工具存放其配置文件的位置而不同。

如果你将 MAST 作为开发依赖而非全局安装,请在以下任何配置中将 mast 替换为 npx @spikedpunch/mast(或 pnpm exec mast)。

Claude Code

claude mcp add mast -- mast serve

添加 --scope project 以将 .mcp.json 写入仓库,这样你的团队就能从 checkout 中获取它。

Claude Desktop

在 macOS 上是 ~/Library/Application Support/Claude/claude_desktop_config.json,在 Windows 上是 %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "mast": {
      "command": "mast",
      "args": ["serve"],
      "env": { "MAST_STATE_DIR": "/absolute/path/to/your/project/.mast" }
    }
  }
}

Claude Desktop 不会在你的项目目录中运行,因此 MAST_STATE_DIR 必须是绝对路径。下面的 CLI 和编辑器集成会从工作目录推断它。

Cursor

项目中的 .cursor/mcp.json,或全局的 ~/.cursor/mcp.json:

{
  "mcpServers": {
    "mast": { "command": "mast", "args": ["serve"] }
  }
}

VS Code (GitHub Copilot)

.vscode/mcp.json:

{
  "servers": {
    "mast": { "type": "stdio", "command": "mast", "args": ["serve"] }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "mast": { "command": "mast", "args": ["serve"] }
  }
}

Zed

settings.json:

{
  "context_servers": {
    "mast": { "command": { "path": "mast", "args": ["serve"] } }
  }
}

任何其他 MCP 客户端

在项目根目录通过 stdio 运行 mast serve。它会通告十一个读取工具,并且除了 serve 之外不需要任何参数。

告诉助手如何使用它

注册服务器会为模型提供工具;但不会告诉它何时使用这些工具,或如何阅读带标记的答案。mast skill 会打印为此编写的说明——将它们粘贴到你的系统提示、CLAUDE.md、.cursorrules 或技能文件中:

mast skill                    # print it
mast skill --install          # splice it into this project's agent config files
mast skill --install --dry-run

--install 只写入已经存在的文件——CLAUDE.md、AGENTS.md、.cursorrules、.windsurfrules、.github/copilot-instructions.md——并且写入标记块内,因此升级后重新运行会替换之前的副本,而不是添加第二个副本。它永远不会自行运行,也永远不会创建你原本没有保留的配置文件。


升级

mast upgrade

这会检查是否有较新的版本,并打印出你安装它的确切命令——它不会就地升级,因为 CLI 无法可靠地区分全局安装和开发依赖,猜错的话会在你的仓库中运行错误的命令。

更重要的是,它会告诉你你的包管理器无法告诉你的一件事:升级是否会改变索引模式。如果会,MAST 会丢弃索引,并在下一次 serve 或 index 时重建它。没有什么是无法重建的——索引是派生状态——但在大型 monorepo 上这需要几分钟,而且提前知道总比发现一个无法解释的停滞要好。


在 monorepo 中使用 MAST

在仓库根目录建立一个索引通常是正确的。跨包导入可以解析,因此 mast_callers 能在兄弟包中找到调用者——这正是使用 monorepo 工具而不是每个包一个索引的原因。

索引的内容。 .ts、.tsx、.js、.jsx 和 .md,减去 node_modules、dist、build、coverage、.next、.turbo、.mast 和测试文件。可以通过 mast init 上的 --extensions 和 --exclude 覆盖,或编辑 .mast/config.json。

其他语言不会被索引,这一点很重要。 MAST 只解析 TypeScript 和 JavaScript。在 Python、Go、Java 或 Rust 中定义的符号不会出现在索引中,这看起来就像在仓库中不存在一样。将空结果视为“MAST 没有找到它”,而永远不要视为“它不存在”——mast skill 也会对模型这样说。

将 .mast/ 添加到 .gitignore。 它是派生状态,体积大,并且是机器特定的。

自定义索引位置不会在运行之间被记住。 --state-dir 仅适用于你传递给它的那一条命令。路径设置被刻意地永远不会从持久化配置中读回——之前一次运行(或之前的容器)写入的绝对路径可能解析到已不存在的位置,或者更糟,解析到属于不同项目的位置。要让自定义位置持久生效,请将其放入源代码控制或环境中:

// mast.config.json, at the project root
{ "state_dir": ".cache/mast" }
export MAST_STATE_DIR=/absolute/path/to/index

解析顺序是 --state-dir → MAST_STATE_DIR → mast.config.json → .mast。mast status 会打印它解析出的目录,并在那里尚未索引任何内容时明确说明。

规模。 VS Code 的冷索引——8,653 个文件,152,969 个分块——大约需要两分钟,并产生一个 794 MB 的状态目录。对已更改文件的增量重新索引只需几毫秒。

CLI 参考

mast init [path]

为项目初始化 MAST 并运行初始完整索引。

Options:
  --state-dir <dir>        Where to write index state (default: <path>/.mast)
  --extensions <ext,...>   File extensions to index (default: .ts,.tsx,.js,.jsx,.md)
  --exclude <pattern,...>  Glob patterns to exclude
  --no-index               Create config only; skip initial indexing

为什么: 创建状态目录结构,写入 config.json,并运行一次完整的解析 + 符号提取过程。提前运行一次意味着后续的增量运行只会处理已更改的文件。


mast search <query> [path]

搜索索引并打印可读的结果。

Options:
  -n, --limit <n>        Max results, 1-50 (default: 10)
  -t, --type <kind>      function | method | class_shell | interface | type | export | block | doc
  -l, --language <lang>  typescript | javascript | markdown
  -e, --exported         Only exported symbols
  -f, --file <glob>      Restrict to files matching a glob
      --state-dir <dir>  State directory
      --json             Emit the raw MCP response instead of text

为什么: 这是检查索引实际包含内容的最快方式,也是 MCP mast_search 工具使用的同一代码路径——它通过注册的处理程序进行分发,而不是重新实现排序,因此 CLI 和助手的结果不可能不一致。过期和截断标志会打印在结果上方;如果空结果是因为索引正忙而为空,也会如实说明。

对于脚本编写,mast query mast_search '{...}' 会给出字节完全相同的 MCP 输出。


mast index [path]

构建或更新索引。

Options:
  --state-dir <dir>    State directory
  --incremental        Only reindex files changed since last run
  --show-progress      Print indexing progress to stderr
  --checker            Opt-in TypeScript-checker pass: upgrades heuristic potential_matches
                        into verified caller edges (or drops non-call-site noise). Can take
                        tens of seconds on a large monorepo — not part of the default path.

为什么是增量: 增量路径会将当前文件清单与存储的 mtime 进行差异比较。只有过期、新增或删除的文件会被处理——对于大型代码库,这会在大多数运行中将索引时间从秒级缩短到毫秒级。


mast serve

通过 stdio 启动 MCP 服务器。

Options:
  --state-dir <dir>         State directory
  --no-startup-reindex      Skip the startup staleness check (not recommended)
  --watch                   Watch source files and incrementally reindex on change
                             (interactive use; not needed in the container ladder)

该服务器实现了四步启动阶梯,因此即使对于大型项目,MCP 客户端也能在一秒内获得可用的服务器。详见 启动阶梯。


mast status [path]

打印索引健康状况。

Options:
  --state-dir <dir>    State directory
  --json               Output as JSON

报告 last_indexed、indexed_files、chunk_count、stale_files、parse_errors、write_errors、index_fresh 和 freshness_cause。使用此命令来诊断搜索结果为何看起来已过期。


mast metrics [path]

显示 token 效率指标。

Options:
  --since <window>        Time window: 7d, 24h, 30m (default: 7d)
  --rollup                Collapse raw rows older than --keep-days into daily roll-ups
  --vacuum                Delete daily roll-up rows older than --keep-days
  --keep-days <n>         Retention days (default: 7 for rollup, 90 for vacuum)
  --state-dir <dir>       State directory

打印一个列对齐的表格:工具名称、调用次数、返回的 token 数、平均持续时间和效率比率。定期使用 --rollup + --vacuum,以防止指标数据库无限增长。


mast install-hooks [path]

安装 git post-commit / post-checkout 钩子,自动运行 mast index --incremental,这样索引就能在提交和分支切换之间保持最新,无需手动操作。


mast query <tool> [json] [path]

直接调用任何 MCP 读取工具,输出与 MCP 传输字节完全相同。

Options:
  --state-dir <dir>   State directory
  --json              Emit the exact single-line MCP response (default pretty-prints)
mast query mast_callers '{"symbol":"resolveConfig"}'
mast query mast_project_skeleton '{}'

为什么: 这是脚本编写和调试的界面。mast search 是通向一个工具的可读前门;而这里可以访问全部十一个工具,并返回助手会收到的完全相同的内容——因此你看到的内容与模型看到的内容之间不可能存在不一致。如果指定一个不存在的工具,它会列出存在的工具。


mast docs [topic]

打印随已安装构建一起发布的文档——readme、spec 或 skill。不带参数会列出主题及其所属版本。

为什么: 省去了读者先查找自己的版本、然后找到不同版本文档的步骤。mast docs 打印的内容就是你的 node_modules 中的二进制文件所做的事情。


mast skill [path]

将 MAST 指令打印到代理提示词、CLAUDE.md、.cursorrules 或技能文件中。

Options:
  --install    Splice into this project's existing agent config files
  --dry-run    With --install, report what would change without writing

为什么: 注册 MCP 服务器能赋予模型工具,但无法赋予其判断力——何时应搜索而非读取、为何查询中代码令牌优于散文,以及如何解读过期或截断标志。它还告诉模型,空结果意味着"MAST 未找到",而非"该内容不存在",这是搜索工具最需要正确理解的一点。


mast upgrade [path]

检查是否有新版本;打印如何安装,以及需要付出什么代价。

为什么: 它会检测 MAST 的安装方式并打印相应的命令,而不是直接执行,因为 CLI 无法可靠地区分全局安装和开发依赖。它还会报告升级是否提升了索引模式——这会导致下次 serve 时进行完全重建索引——而你的包管理器无法告诉你这些。


MCP 工具参考

MAST 向 MCP 服务器注册了 11 个工具。每个读取工具都包含一个 _stats 块:

{
  tool: string,
  tokens_returned: number,
  tokens_full_file_upper_bound: number,
  files_referenced: string[],
  efficiency_ratio: number,           // 1 - (returned / full_file)
  duration_ms: number,
}

对已索引的代码库执行词法 BM25 + 声明精确搜索。

{
  query:         string,              // natural language or identifier
  limit?:        number,              // max results (default 10, max 50)
  language?:     "typescript" | "javascript" | "markdown" | null,
  file_pattern?: string | null,       // glob: "src/api/**"
  chunk_type?:   "function" | "method" | "class_shell" | "interface" | "type" | "export" | "block" | "doc" | null,
  only_exported?: boolean
}

返回: { results[], suggestions?, _stats }。每个结果包含 file_path、start_line、end_line、content、chunk_type、symbol_name、parent_symbol、is_exported、match_score(BM25 分数,为负值;当命中仅来自排名器 D 时为 null)、rank、match_snippet,以及可选的 related 提示(当方法和其类外壳同时匹配时;仅返回排名较高的那个)。suggestions 仅在 results 为空时出现,可能为空——即零结果时的"您是不是要找"辅助。

为什么: grep 和 glob 查找精确字符串,要求调用者已经知道模式。mast_search 通过倒数排名融合(Reciprocal Rank Fusion)融合两个信号进行相关性排名:

  • BM25(FTS5,trigram 分词) —— 通用词法排名器;处理关键词查询和子令牌/驼峰匹配。

  • 排名器 D(声明精确) —— 直接匹配块自身的 symbol_name(全名或最后点号段,不区分大小写)。捕获 BM25 的 trigram 评分可能低估的精确符号查询。由 declaration_exact_ranker 配置键控制(默认开启);关闭时,mast_search 仅使用 BM25。

两个排名器都命中的块排名高于仅一个排名器找到的块。file_pattern 和 language 限制两个排名器提取的池子,因此作用域搜索永远不会返回作用域之外的文件。file_pattern 是一个 glob,使用与索引时应用 exclude_patterns 相同的原语进行匹配:* 不跨 /,** 跨 /,? 是一个非 / 字符,匹配区分大小写,其他所有字符——.、_、-——均为字面量。


mast_project_skeleton

按文件分组的所有导出符号,可选择限定到某个目录。

{
  directory?:    string | null,       // path prefix: "src/api"
  max_depth?:    number,              // max subdirectory depth (default unlimited)
  file_pattern?: string | null        // glob filter on file paths
}

返回: { files: [{ file_path, exports: string[] }], _stats }。

为什么: 在浏览代码库之前,助手需要定位——"这里有什么?"。读取每个文件来查找其导出是低效的。mast_project_skeleton 通过一次调用返回按目录限定的文件 → 导出名称映射,让助手无需打开任何文件即可建立子系统的心理模型。


mast_exports

单个文件的所有导出符号,包含类型签名和 TSDoc。

{
  file_path: string                   // relative to project root
}

返回: { file_path, exports: [{ name, kind, signature, line, doc }], _stats }。

为什么: 这是 mast_project_skeleton 的自然后续。一旦助手知道哪个文件相关,mast_exports 提供完整签名而无需函数体——足以理解模块的公共接口,而无需付出实现的代价。

方法被有意省略(它们通过其父类的 mast_signature 呈现),因此结果保持聚焦于模块的公共契约。


mast_signature

命名符号的声明、TSDoc 和解析后的参数类型上下文。

{
  symbol:     string,                 // e.g. "handleLogin", "AuthService"
  file_path?: string | null           // narrow to a specific file
}

返回: SignatureResult 数组,每个包含 symbol、file_path、line、signature、doc、params、return_type 和 type_context。

type_context 自动填充:签名中出现的用户定义的 PascalCase 类型名通过三优先级查找解析为其自身签名——先同文件,再命名导入,最后是全局导出类型回退。长签名在 500 个字符处截断。这意味着一次 mast_signature 调用即可为助手提供函数的完整类型图景,无需单独查找。

为什么: 当助手看到 function processOrder(order: Order, ctx: RequestContext): Promise<Result> 时,了解 Order、RequestContext 和 Result 的签名对于理解函数功能至关重要。mast_signature 无需再进行三次工具调用,而是内联解析它们。


mast_callers

谁调用了给定符号,分为已验证调用者(来自符号图)和潜在匹配(来自全文标识符搜索)。

{
  symbol:              string,
  file_path?:          string | null,
  transitive?:         boolean,       // walk the full call chain (default false)
  include_potential?:  boolean        // include identifier_fts matches (default true)
}

返回: { verified_callers[], potential_matches[], summary: { verified_count, potential_count, transitive, checker_classified_non_call_site, checker_classified_different_declaration }, _stats }。

为什么: 重构前的影响分析需要知道谁依赖某个符号。已验证调用者通过图解析(确定性,无名称冲突误报)。潜在匹配是标识符 FTS 命中,其中调用无法静态解析——它们可能是误报,但值得审查。将两者分开让助手能够推理置信度:如果 verified_count 为 3 且 potential_count 为 0,则重构范围已明确。如果 potential_count 为 15,则存在更多不确定性。运行 mast index --checker 可将一些潜在匹配升级为已验证边(或丢弃非调用点噪声)——checker_classified_* 计数报告数量,当检查器遍从未运行时为 0。


mast_dependencies

文件记录的所有导入。

{
  file_path: string
}

返回: { file_path, imports: [{ module, symbols[], is_external, resolved_path? }], _stats }。

为什么: 理解文件的依赖面是推理其功能的第一步。外部导入(无 resolved_path)会被标记,以便助手了解解析边界。内部导入包含解析后的路径,以便调用者可以跟踪链。


mast_implementors

实现给定接口的所有具体类及其方法列表。

{
  interface_name: string
}

返回: { results: [{ class_name, file_path, line, methods[] }], _stats }。

为什么: 在依赖注入代码库中,interface_name → implementors 是"这里实际运行什么?"的答案。MAST 不是在索引时存储显式的 IMPLEMENTS 边,而是使查找即时且结构正确。


mast_rename_impact

重命名符号的组合重构检查清单:声明点、已验证调用者、潜在匹配和桶文件再导出,一次调用完成。

{
  symbol:     string,
  file_path?: string | null
}

返回: { symbol, declaration_sites[], verified_callers[], potential_matches[], barrel_exports[], summary: { declaration_count, verified_count, potential_count, barrel_count, checklist, checker_classified_non_call_site, checker_classified_different_declaration }, _stats }。

为什么: 重命名不仅涉及调用点——桶文件再导出(export { Foo } from './foo',可能带别名)也需要更新,而普通调用者搜索很容易遗漏。mast_rename_impact 将 mast_callers 的机制与桶文件导出检测组合,使助手获得一个检查清单,而不是三次单独查询。


mast_reindex

在 MCP 会话中触发同步重建索引。

{
  full?: boolean                      // force full reindex (default: incremental)
}

返回: { files_indexed, files_skipped, chunks_added, chunks_removed, parse_errors, write_errors, duration_ms }。

为什么: 长时间运行的编辑会话会积累过期数据——新符号和文件在索引之前不会被 mast_search 找到(JIT 过期处理保持已索引文件的行坐标在读取时正确,但无法发现全新文件或符号)。mast_reindex 让助手按需刷新索引——例如,在大型重构之后——而无需离开 MCP 会话。当怀疑增量状态损坏时,可使用 full 标志。


mast_status

索引的健康快照。

// no inputs

返回: { state_dir, last_indexed, indexed_files, chunk_count, stale_files, parse_errors, write_errors, index_fresh, freshness_cause, seed_commit? }。

index_fresh 仅在 stale_files = 0 且索引至少运行过一次时为 true。当存在过期文件时,freshness_cause 为 "phase1_stale",新鲜时为 null。stale_files 计算已更改的文件、磁盘上存在但完全不在索引中的文件,以及磁盘上已删除的已索引文件——与 mast status 报告的数字相同,来自同一生产者。

为什么: 在依赖准确代码导航的长时间代理工作流之前,助手可以调用 mast_status 确认索引是否新鲜,如果不新鲜则向用户显示过期文件数。


mast_efficiency

当前会话或所有时间的令牌节省报告。

{
  scope:          "session" | "global",
  since_minutes?: number              // global scope: restrict to last N minutes
}

返回: { scope, window_started_at, tokens_returned, tokens_full_file_upper_bound, efficiency_ratio, calls_total, calls_by_tool, tokenizer, counterfactual }。

counterfactual 字段是一个人类可读的句子:"使用朴素的完整文件读取将花费 ~14,200 个令牌;节省了 ~11,400 个令牌(80.3%)。"

为什么: 令牌效率是 MAST 存在的全部原因,但没有度量就只是空谈。每次工具调用都会将返回的令牌异步记录到 metrics(即发即弃,< 1 毫秒)。mast_efficiency 聚合这些记录,使精确代码导航的价值具体且可审计。


配置

MAST 从项目根目录的 mast.config.json、环境变量或 CLI 标志读取配置。优先级顺序(从高到低):CLI 标志 → MAST_STATE_DIR 环境变量 → mast.config.json → 内置默认值。

Key

默认值

描述

state_dir

.mast

所有索引状态的目录(相对于项目根目录)

file_extensions

.ts,.tsx,.js,.jsx,.md

要索引的源文件扩展名

exclude_patterns

node_modules/**、dist/**、coverage/**、.kluster/**、**/*.test.ts、**/*.spec.ts

要跳过的 Glob 模式

rrf_k

60

倒数排名融合常量(值越大排名越平坦)

declaration_exact_ranker

true

将排名器 D(声明精确匹配)融合到 mast_search 中。设为 false 可在不修改代码的情况下恢复仅 BM25 排名。

chunk_split_threshold

100

超过该行数的声明将被拆分为重叠的子块

context_lines

3

存储在内容中的 AST 边界前后的源代码行数

markdown_heading_depth

2

开始新 Markdown 文档块的最大 ATX 标题级别(##)

mast.config.json 示例:

{
  "state_dir": ".mast",
  "exclude_patterns": ["node_modules/**", "dist/**", "**/*.test.ts"],
  "declaration_exact_ranker": true,
  "context_lines": 5
}

MAST_STATE_DIR — 覆盖状态目录,无需修改 mast.config.json。在项目根目录为只读的 CI 或 Docker 环境中非常有用。


工作原理

索引

runIndex 使用 fast-glob 遍历项目,计算基于 mtime 的清单,并将其与存储的清单进行差异比较,以找出过期、新增和已删除的文件。对于每个需要处理的文件:

  1. 解析 — tree-sitter 将文件解析为具体语法树。.ts 和 .tsx 文件使用 TypeScript 语法;.js 和 .jsx 文件使用 JavaScript 语法。Markdown 文件按标题(markdown_heading_depth)分块,而非使用 tree-sitter 解析。

  2. 分块 — 提取器将 CST 分解为类型化块:function、class_shell(类声明及成员签名,不含函数体)、method(单个方法)、interface、type、export、block 和 doc(Markdown 章节)。类始终会被分解,这样搜索单个方法时不会返回整个类体。

  3. 子块拆分 — 超过 chunk_split_threshold 行的声明会被拆分为重叠片段,确保没有任何单个块过大而无法成为有用且自包含的搜索结果。

  4. 符号图 — 符号、导入和边(IMPLEMENTS、PARENT_OF、POTENTIAL_CALL)被写入 SQLite。两遍写入策略(先写所有文件,再写边)确保边可以引用在同一轮运行中稍后解析的文件中定义的符号。

  5. FTS — 块内容被写入带有 trigram 分词器的 FTS5 虚拟表,支持子词元和 camelCase 搜索。带有 unicode61 分词器的 identifier_fts 表处理 mast_callers 潜在匹配的精确标识符查找。

索引是单阶段的——块/图/FTS 在单次 runIndex 遍历中一起更新;没有单独的嵌入步骤。

排名搜索(BM25 + 通过 RRF 融合的排名器 D)

查询经过两个排名器:

BM25(FTS5): 查询使用 SQLite 内置的 BM25 排名(基于 trigram 分词器)对 chunk_fts 进行匹配。文件模式过滤器和语言过滤器作为针对 files 表的 SQL 谓词推入该查询(而非 FTS MATCH 谓词,因为 SQLite FTS5 对 UNINDEXED 列使用 LIKE 与 MATCH 结合时不可靠)。BM25 分数在 SQLite 的约定中为负值——越负表示匹配越强;mast_search 的 match_score 保留该符号。

排名器 D(声明精确匹配): 针对 chunks.symbol_name 的直接 SQL 谓词——全名匹配或最后点号段匹配,不区分大小写,确定性排序。由 declaration_exact_ranker 配置键控制(默认开启)。

RRF 融合: 两个排名列表使用倒数排名融合进行合并:

score(chunk) = Σ 1 / (k + rank(chunk))

默认 k = 60。在两个列表中都排名第 1 的块,其得分是仅出现在一个列表中的块的两倍。仅出现在一个列表中的块仍然得分良好——两种信号都不会占主导地位。

JIT 过期检查

每个读取工具(搜索、导出、签名、调用者、依赖、实现者)在返回结果之前都会调用 jitRefreshFile。该函数:

  1. 从 files 表中读取文件的存储 mtime。

  2. 对磁盘上的文件调用 stat()。

  3. 如果磁盘 mtime 较新,则获取 structure.lock 并立即重新解析该文件。

这意味着助手编辑文件后立即查询时,始终能看到当前版本,无需等待计划中的重新索引。(JIT 过期检查处理索引已知的文件;全新文件或符号仍需要 mast_reindex 或下一次计划/监视重新索引才能被发现。)

启动阶梯

mast serve 通过四步阶梯在 1 秒内开始接受 MCP 连接:

Step 1  Bootstrap state directory; copy Docker seed layer if present;
        best-effort remove orphaned pre-vector-store state              < 500ms
Step 2  Schema version check; open SQLite                               < 1s
Step 3  Register all 11 MCP tools; open stdio transport                 < 500ms
Step 4  Background incremental reindex                                  async

第 3 步完成后,所有工具即可提供服务——不存在功能缩减的启动窗口。当预构建的种子索引在 /opt/mast-seed 可用时,它会在第 1 步被复制到状态目录——第 4 步的后台重新索引随后只需处理自种子索引构建以来发生变化的文件。

并发模型

一个咨询锁协调并发写入者:

  • structure.lock — 由 runIndex 和 JIT 重新解析持有。防止两个写入者同时修改 SQLite 图。

该锁使用 proper-lockfile(通过 .lock 标记文件实现 POSIX 咨询锁)。10 秒的过期锁超时防止崩溃进程无限期阻塞系统。读取工具从不获取写锁——在并发重新索引期间它们可能看到短暂不一致的状态,并在这种情况下返回 file_busy_returning_stale_cache: true。

存储布局

.mast/
  graph.db              SQLite — symbols, edges, imports, chunks, FTS5 tables, metrics
  file_manifest.json    mtime snapshot from the last index run
  index.json            schema version, file count, chunk count, last_indexed
  config.json           resolved config written at init/serve time
  structure              lock marker (proper-lockfile target)

令牌效率

每次工具调用都会异步将其令牌数记录到 metrics。记录包括:

  • tokens_returned — 响应中的实际令牌数(Anthropic CL100k 分词器)

  • tokens_full_file_upper_bound — 朴素的全文件读取本应花费的令牌数(可计算时)

  • duration_ms、session_id 和 status

metrics_daily 按 (day, tool_name) 汇总这些数据,包含持续时间的运行平均值和令牌数的运行总计。汇总 upsert 使用增量平均公式,避免无限期存储所有原始行:

avg_duration_ms = (old_avg * old_n + new_val) / (old_n + 1)

使用 mast metrics --since 7d 获取人类可读的表格,或在 MCP 会话中使用 mast_efficiency 获取带有 counterfactual 叙述的机器可读 JSON 摘要。


历史

MAST 最初将 BM25 与向量嵌入搜索分支(LanceDB + 本地 ONNX 嵌入模型)融合。测量结果不支持保留它:向量存储根据 M2 决策于 2026-08-06 被移除(参见 ADR 003)。删除前的系统——包括嵌入流水线和测量它的评估工具——保留在 git 标签 mast-pre-vector-delete 处,供任何重新运行该证据的人使用。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Fast semantic code search for AI agents — find symbols, references, and callers across any codebase.
    9
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Token-safe code search for AI agents: queries the language-server index (clangd / Roslyn / tsserver / pyright) instead of grep and returns a token-capped file:line list — ~20x fewer tokens. Symbol-level editing + a grep→index rewrite hook. Local-only, no IDE.
    16
    13
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides graph-native code intelligence for coding agents, enabling them to navigate repositories through compact SQLite-backed symbol graphs and retrieve full source only for relevant symbols.
    -