codebase-memory-mcp 评测方法论:可复现地量化答案质量、延迟稳定性与 Agent 节省量
【免费下载链接】codebase-memory-mcpHigh-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.项目地址: https://gitcode.com/GitHub_Trending/co/codebase-memory-mcp
本文基于 codebase-memory-mcp(下称 CBM)仓库中的 MEASURING_SAVINGS.md 展开,讲解如何在自己的仓库上独立复现"答案质量、图查询延迟与稳定性、Agent 模型 token / 工具调用节省量"三类指标的完整测量流程。读完并照做之后,你能得到一份带冻结 SHA、隔离会话和原始计数依据的评测记录,而不是把"查询很快"误读成"回答更对"或"更省 token"。
为什么必须把三类问题分开测量
CBM 把代码库索引为持久知识图谱,README 公布的 性能数据(如"五个结构性查询约 3,400 tokens vs 文件逐个探索约 412,000 tokens")与 31 个真实仓库的评估结果(83% 答案质量、token 与工具调用数下降)是发布层面的结论。要在自己的仓库上得出可辩护的结论,必须回答三个彼此独立的问题:
- Agent 给出的答案是否更好?
- CBM 在该负载下是否快速、稳定?
- 基于图(graph)的探索是否比逐文件(file-by-file)探索使用更少的模型 token 或工具调用?
三者不可互相替代:一次快速的图查询不能证明最终答案正确;而 CBM 内部的查询计数器(query_count)只能统计 CBM 侧的工具调用,看不到 Agent 的模型 token 消耗,也看不到非 CBM 的工具调用(如文件列表、文本搜索、文件读取)。因此三类指标必须分开测量、分开报告。README 的 性能章节、语言级基准 BENCHMARK.md(PASS/PARTIAL/FAIL 评分体系)以及更完整的对比方法论 EVALUATION_PLAN.md 提供了评分概念和更广泛的对照实验框架,而 MEASURING_SAVINGS.md 是一份可直接执行的"小配方"。
第一步:冻结实验
文档中所有 shell 示例要求 POSIX 兼容 shell(macOS/Linux,或 Windows 上的 Git Bash),不是原生 PowerShell 语法。所有命令块要在同一个专用 shell 中顺序执行:set -eu提供 fail-fast 语义,且脚本会先捕获 Git 命令输出再测试,防止内层命令失败被误判为"工作区干净"。
实验前必须记录的信息
在任何条件开始之前,先记录:
- 仓库 URL 或本地标识,以及精确的 Git commit SHA;
- 问题集,以及每个问题期望答案的范围(expected scope);
- CBM 版本、索引模式(index mode)、操作系统与机器配置;
- Agent 模型/版本、系统提示词、工具说明、上下文上限、每问题预算;
- 哪个条件先跑,以及任何预热(warm-up)策略;
- 用于记录 token 与工具调用的客户端或评测 harness。
两个条件必须使用相同的仓库 SHA、问题集、模型、提示词、预算和"干净会话"策略;不要让第二个条件看到第一个条件的答案或工具结果。如果重复实验,重复次数与聚合方法必须在看结果之前定好,并保留每一次运行——包括失败和零结果查询。
为两个条件创建干净的 detached worktree
不能只在"看起来干净"的工作副本上跑实验:冻结实验必须连未跟踪文件(untracked files)一起考虑。因此为两个条件各建一个独立的干净 detached worktree,并把产物目录放在两个 worktree 之外:
set -eu SOURCE_REPO=/absolute/path/to/source-repository COMMITISH=main SHA=$(git -C "$SOURCE_REPO" rev-parse "$COMMITISH^{commit}") || exit 1 MODE=full RUN_ROOT=$(mktemp -d) || exit 1 GRAPH_REPO="$RUN_ROOT/graph" BASELINE_REPO="$RUN_ROOT/baseline" ARTIFACT_ROOT="$RUN_ROOT/artifacts" git -C "$SOURCE_REPO" worktree add --detach "$GRAPH_REPO" "$SHA" || exit 1 git -C "$SOURCE_REPO" worktree add --detach "$BASELINE_REPO" "$SHA" || exit 1 mkdir "$ARTIFACT_ROOT" || exit 1 GRAPH_SHA=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1 GRAPH_STATUS=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1 BASELINE_SHA=$(git -C "$BASELINE_REPO" rev-parse HEAD) || exit 1 BASELINE_STATUS=$(git -C "$BASELINE_REPO" status --porcelain=v1 --untracked-files=all) || exit 1 test "$GRAPH_SHA" = "$SHA" || exit 1 test -z "$GRAPH_STATUS" || exit 1 test "$BASELINE_SHA" = "$SHA" || exit 1 test -z "$BASELINE_STATUS" || exit 1要点:
status --porcelain=v1 --untracked-files=all会把未跟踪文件也算作脏,test -z断言其输出为空才算干净;- 在索引之前、以及每个条件开始前,都要再重复一遍对应的 SHA 与干净性断言;
- 断言失败时停止并新建 detached worktree,不要用删除未知文件的方式把一个复用过的 checkout"洗"成干净状态;
- 所有运行产物写入
ARTIFACT_ROOT,避免污染任一条件 worktree。
第二步:图条件预检(Preflight)
每次测量 Graph 条件之前,立即重跑该 worktree 的干净性断言,并要求对这个精确 worktree、这个模式做一次成功的全新索引:
set -eu GRAPH_SHA=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1 GRAPH_STATUS=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1 test "$GRAPH_SHA" = "$SHA" || exit 1 test -z "$GRAPH_STATUS" || exit 1 codebase-memory-mcp cli index_repository \ --repo-path "$GRAPH_REPO" \ --mode "$MODE" || exit 1 GRAPH_SHA_AFTER=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1 GRAPH_STATUS_AFTER=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1 test "$GRAPH_SHA_AFTER" = "$SHA" || exit 1 test -z "$GRAPH_STATUS_AFTER" || exit 1索引前后各做一次 SHA/干净性断言,防止索引过程改变了仓库状态。CLI 模式本身不启动协调守护进程,只持有命令生命周期的准入租约,详见 README 的 CLI Mode 章节。
从响应中提取项目名,并做状态交叉核对
从成功的索引响应中取PROJECT_NAME。verbose 的 status 调用只适合做 root/当前 HEAD 的交叉核对:
codebase-memory-mcp cli list_projects codebase-memory-mcp cli index_status --project PROJECT_NAME --verbose核对root_path是否等于GRAPH_REPO、git.head_sha是否等于SHA、Git 上下文是否 detached。这里有一个关键的证据边界:index_status描述的是项目根目录及其当前文件系统 Git 上下文,它本身不能证明被索引的记录来自该修订版本。真正的新鲜度依据是"紧邻其前的一次成功的全新index_repository调用"。
代表性查询验证
最后跑一条或多条代表性查询,其期望符号必须在记录的 SHA 上直接人工验证过:
codebase-memory-mcp cli search_graph \ --project PROJECT_NAME \ --name-pattern 'KNOWN_SYMBOL_PATTERN' \ --limit 10只有当三件事同时成立才能开始测量:全新索引成功、status 交叉核对指向预期的 detached checkout 与当前 SHA、代表性查询返回期望符号。把索引响应、status 输出和查询输出与本次运行产物一起保存。在跑 file-by-file 条件前,同样对BASELINE_REPO重跑一遍 SHA 与干净性断言。
测量一:答案质量(Answer Quality)
构造一个固定的真实开发者问题集,覆盖这些维度(凡适用的话):定义发现、关系或调用路径、定向源码检索、架构、横切关注点问题。每个问题都要记录期望范围,或一份独立推导出的 ground truth。
两个隔离条件的工具面:
| 条件 | 允许的探索工具 |
|---|---|
| Graph | CBM 图工具:search_graph、trace_path、query_graph、get_code_snippet、get_architecture、search_code |
| File-by-file 基线 | 仅文件列表、文本搜索、定向文件读取 |
评分时对照冻结 SHA 上的源码,而不是对照"听起来是否可信"。BENCHMARK.md 中的精简 rubric 用 PASS (1.0)、PARTIAL (0.5)、FAIL (0.0) 计分,并把真正不适用的题目从分母中剔除。要做更细粒度的比较,则遵循 EVALUATION_PLAN.md:正确性、完整性、具体性分开打分,评分者对"哪个条件产出了哪个答案"保持盲评,A/B 顺序随机化,且评分必须附源码证据。
最后一条纪律:质量分数要与效率指标并排放置但分开统计。token 下降只有当答案仍然达到选定的质量门槛时才有意义。
测量二:延迟与稳定性
索引耗时与查询延迟分开记录
索引时间和查询延迟分开计时,并把索引运行分类为 full-source、artifact-assisted 或 incremental。这里有一个仓库特有的判定细节:当本地没有项目数据库时,index_repository可以先导入兼容的.codebase-memory/graph.db.zst工件,再走增量清单路线(该机制即 README 的 Team-Shared Graph Artifact 章节)。计时之前要记录GRAPH_REPO中该工件是否存在、保留索引日志,并且必须在日志中找到一条包含db与size_mb的成功artifact.import记录,才能把这次运行归类为 artifact-assisted。
这一点可以直接在源码中验证。src/pipeline/artifact.c 中,导入成功后写入的记录正是:
// src/pipeline/artifact.c#L1201-L1202 cbm_log_info("artifact.import", "db", cache_db_path, "size_mb", itoa_buf((int)((size_t)dlen / ART_BYTES_PER_MB)));而失败或跳过路径记录的是skip(如 schema 版本不匹配)或err。所以文档的告诫是准确的:仅仅尝试过 bootstrap,或一条包含skip/err的artifact.import记录,都不构成"工件被使用"的证据。
对查询侧:使用同一固定工作负载、同一顺序,提前标记预热调用;保留每次调用的耗时与退出状态,而不是只留一个平均值。报告时要注明机器、操作系统、CBM 版本、仓库 SHA、索引模式、问题集、索引运行类别,以及查询结果是冷(cold)还是热(warm)。
内置诊断:CBM_DIAGNOSTICS
对于 daemon 支撑的运行,在第一个会话启动之前启用诊断。daemon 在启动时捕获环境;如果它已经在运行,必须先关闭所有 daemon 支撑的会话再改设置。完整环境契约见 CONFIGURATION.md 第 4 节:
export CBM_DIAGNOSTICS=1CBM 会在系统临时目录下新建一个随机化、仅属主可读的诊断目录——不要假设或手工构造旧的、可预测的/tmp文件名。精确的snapshot与trajectory路径要从${CBM_CACHE_DIR}/logs/cbm-daemon.log(默认缓存目录为~/.cache/codebase-memory-mcp)中的diagnostics.startJSON 记录中发现。该记录通过控制级日志通道发出,即使配置了抑制普通日志的日志级别也会输出——源码中可以确认这一设计意图(src/foundation/diagnostics.c 的注释明确写着"Discovery must survive CBM_LOG_LEVEL suppression"):
// src/foundation/diagnostics.c#L670-L671 cbm_log_control("diagnostics.start", "snapshot", g_diag_path, "trajectory", g_diag_ndjson_path, "interval_s", interval);两个诊断文件的作用:
snapshot.json(实时快照):包含 CBM 侧的query_count、query_errors、query_total_us、query_avg_us、query_max_us以及进程资源计数器(见 src/foundation/diagnostics.c#L593-L597 的 JSON 输出);trajectory.ndjson(留存文件):提供资源与查询计数随时间变化的趋势。
这两份数据适合做 CBM 延迟与稳定性分析,但不是Agent 模型用量或非 CBM 工具调用的记录。README 的 Troubleshooting & Diagnostics 章节解释了文件的完整内容与保留/轮转行为。另外,一个 daemon 可能被多个会话共享:归因计数器到单一工作负载时,应使用"否则处于空闲"的 daemon,记录 before/after 值,或启动专用运行。
标准 Soak 工作负载
从源码 checkout 出发,标准的耐力(endurance)入口是:
scripts/soak-legs.sh build/c/codebase-memory-mcp 10它依次执行 quick 混合负载与只读的 query-leak 负载,检查存在合法的完成摘要,并写出每次调用的延迟与资源结果。仓库中的 scripts/soak-legs.sh 证实了这一序列的所有权:它把quick与query-leak两条 leg 都转发给scripts/soak-test.sh,并带一个完成摘要守卫——任何一条 leg 即使退出码为零、只要日志中没有=== soak-test: PASSED ===摘要,就按失败处理(见 soak-legs.sh 的 run_leg 守卫)。具体而言:
- quick leg 写入
soak-results/;query-leak leg(CBM_SOAK_MODE=query-leak --skip-crash-test)写入soak-results-query-leak/; - 每个目录包含
latency.csv、metrics.csv、summary.txt; - 两个 CSV 以如下精确表头开头(可在 scripts/soak-test.sh#L164-L165 中逐字核对):
timestamp,tool,duration_ms,exit_code timestamp,uptime_s,rss_bytes,heap_committed,fd_count,query_count,query_max_us不要直接调用scripts/soak-test.sh:soak-legs.sh拥有发布门禁序列。query-leak leg 的语义值得注意:它完成首次索引后不再索引或变更任何数据,因此任何 RSS 增长都只能来自查询路径的泄漏,而不是 WAL/索引抖动——这是把"稳定性"证据做得干净的一个小技巧。
这些产物只能作为稳定性与 CBM 延迟证据,不能作为答案质量或模型 token 证据。
测量三:Token 与工具调用节省
在同样的冻结输入上跑 Graph 与 file-by-file 两个条件。模型最终输入/输出 token 数与 Agent 总工具调用量只能由 MCP 客户端或评测 harness 捕获——CBM 自己不可能知道。
数据行模式
把每个run_id定义为一个成对的实验重复(paired replicate);每个(run_id, condition)恰好是一个回答一个问题的隔离客户端会话。对客户端直接测量的每个 usage window 记一行:
run_id,condition,repo_sha,question_id,window,input_tokens,output_tokens,total_tokens,tool_calls,wall_time_ms,answer_artifact,quality_score行内tool_calls的含义是该同一窗口内客户端全部工具调用的计数:包括编排调用、该条件允许的图/文件工具、重试、错误、零结果调用——不要只数 CBM 调用。候选窗口有两个:
- Answering tokens:围绕答题阶段固定标记之间的输入+输出 token;
- Full-session tokens:整个隔离会话,包括定位(orientation)、初始探测、死胡同、答案格式化。
full-session 数值最能代表采纳者的总成本;answering 窗口帮助解释差异从何而来。两条红线:不要用 CBM 的query_count冒充tool_calls(它看不见文件搜索、文件读取或其他客户端工具);不要推断缺失的窗口、把会话总量拆分到多个问题、或把一个会话总量复制到多个问题行。
计算口径
对每个 Graph/baseline 对,只比较双方客户端都直接报告过的窗口,每个(run_id, condition, window)至多一行;某窗口任一方无法暴露,就省略该窗口或记 N/A 并排除出比较。选定一个窗口W,只用同一窗口的行计算:
token reduction (%) = 100 * (baseline tokens - Graph tokens) / baseline tokens tool-call reduction (%) = 100 * (baseline calls - Graph calls) / baseline calls token ratio = baseline tokens / Graph tokens tool-call ratio = baseline calls / Graph calls每个共同测量的窗口都要分别计算并分别标注;绝不混合不同窗口的分子分母。边界规则:baseline token/调用为零时,对应 reduction 百分比记 N/A;Graph token/调用为零时,对应 ratio 记 N/A;不要加伪计数(pseudocount)。发布结果时,比率与百分比必须与原始成对计数一起给出,并同时发布每对的质量结果、运行次数、聚合方法、失败情况和实验控制项。最后一个警告:绝不要把某一个仓库、问题集、模型或机器上的结果变成普适的节省量声明。
复现性清单
原文档以一份检查单收尾,它本身就是文章最重要的自检工具:
- 记录了仓库身份与精确 SHA;每个条件使用无 tracked/untracked 变更的干净 detached worktree;
- 保留了 CBM 版本、索引模式、项目名与预检输出;
- 每次 Graph 运行都紧跟一次对冻结 worktree 和模式的成功全新索引;verbose status 只是 root/当前 HEAD 的交叉核对;
- 问题、ground truth、提示词、预算、条件顺序全部冻结;
- Graph 与 file-by-file 运行使用隔离会话与相同控制变量;
- 质量、CBM 延迟/稳定性、Agent 节省量分开报告;
- 用量计数直接来自客户端或评测 harness;每个条件运行是一个隔离的问题/会话;共同窗口用独立行;不支持的窗口省略或记 N/A,不做推断;
- 诊断路径来自
diagnostics.start,而非猜测的临时路径; - 保留原始输出、错误、零结果与计算输入;
- 每个结论都标明其仓库、SHA、问题集、模型、机器与运行次数,不含臆造或外推的基准数字。
小结
这份方法论文档的价值不在于任何单一命令,而在于证据边界:SHA 断言 + 全新索引回答"图来自哪个版本",artifact.import的成功记录回答"工件是否真的被使用",diagnostics.start回答"诊断文件在哪",客户端 harness 的 usage window 回答"Agent 实际花了多少"。把这些边界逐一钉死之后,你在自己仓库上得到的质量、延迟与节省量数字,才具备与 README 发布数字对等的可复核性。
【免费下载链接】codebase-memory-mcpHigh-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.项目地址: https://gitcode.com/GitHub_Trending/co/codebase-memory-mcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考