CodeGraph 索引指南:init 一步建图、增量 sync 与 MCP 会话中的三层自动保鲜机制
CodeGraph 把项目预先索引为一张本地代码知识图谱(存储于项目根的 .codegraph/ 目录),供 Claude Code、Codex、Gemini、Cursor、OpenCode、Antigravity、Kiro、CoPilot 等 AI Agent 通过 MCP 调用。本文围绕 官方索引指南 展开:先讲清 init / index / sync 三类命令的分工,再深入文件监听器、防抖同步、按文件陈旧度横幅与连接时追赶这三层"自动保鲜"机制的源码实现,最后说明如何验证索引是否跟手、以及极少数的手动同步场景。读完你可以掌握:如何一步初始化索引、如何让图谱在 Agent 会话期间与磁盘代码保持同步、以及当 Agent 询问"索引是否已追上"时该看哪个输出块。
一步完成初始化与全量索引
cd your-project
codegraph init # 创建 .codegraph/ 并构建全量图谱 —— 一步到位
codegraph init 在同一个步骤内完成两件事:创建本地 .codegraph/ 数据目录,并构建全量图谱。不存在"init 之后再单独跑 index"的环节——建图完成后,图谱的保鲜就交给后文所述的自动机制。
三个索引命令的分工:
codegraph index # 全项目全量索引
codegraph index --force # 从头重建(丢弃既有索引)
codegraph sync # 增量同步 —— 只重新解析发生变化的文件
sync 之所以快,是因为它只重新解析变化过的文件——文件监听器在每次编辑后替你跑的就是它,日常几乎不需要手动执行。从源码结构看,sync 内部有一条"作用域快速路径":当待处理文件集合是监听事件直接给出的精确文件列表、且规模不超过阈值(watcher.ts 中 SCOPED_SYNC_MAX_PENDING = 500)时,直接把这些路径交给同步流程、跳过 O(仓库规模) 的扫描比对;只有当事件无法完整描述变化(如目录删除、事件风暴超过 500 个文件)时,才回退到全量扫描比对作为"地面真值"。
三层自动保鲜:编辑到下一次查询之间,Agent 不会拿到静默的错答案
在 Agent 会话中你不需要手动跑 codegraph sync。 当你的 Agent 通过 codegraph serve --mcp 启动 MCP 服务时,三层机制协同保证索引与代码同步,并且在"编辑发生"到"下一次同步完成"之间那个小窗口里,绝不让 Agent 拿到一个静默的错误答案。
第一层:文件监听器 + 防抖自动同步(始终开启)
MCP 引擎在打开项目后会启动一个原生文件监听器:macOS 上走 FSEvents,Linux 上走 inotify,Windows 上走 ReadDirectoryChangesW,监听范围是整个项目根目录。每个源文件的创建 / 修改 / 删除事件都会被捕获,一个防抖定时器把成串的编辑合并为一次同步。
事件流的典型时序:
agent writes src/Widget.ts
→ watcher fires (event delivery: typically <100ms)
→ 2000ms debounce
→ sync runs; Widget.ts's nodes + edges are in the index
→ next agent query sees it
可调参数:环境变量 CODEGRAPH_WATCH_DEBOUNCE_MS 可覆盖默认 2000ms 的防抖窗口,取值被钳制在 [100ms, 60s]。当构建步骤或格式化工具在短时间内密集写大量文件时,把它调大到 5000 或 10000,让监听器把这些写入合并成一次同步。
源码层面这个参数在 engine.ts 的 parseDebounceEnv() 中解析:非数字、非整数或越界值一律视为"忽略这个错误配置",回退到 FileWatcher 的默认 2000ms——设计上选择"不悄悄钳位",因为把 0 或笔误值静默封顶会掩盖真实的配置错误;生效值会打印到 stderr(File watcher debounce: <ms>ms (CODEGRAPH_WATCH_DEBOUNCE_MS))以便排查。
watcher.ts 中的实现还有两个超出文档细节的要点,解释了为什么这套监听器既快又省资源:
- 平台策略是"有界"的:macOS/Windows 使用单个递归
fs.watch(libuv 映射为一条 FSEvents 流 / 一个 ReadDirectoryChangesW 句柄),无论树多大都只占 O(1) 描述符;Linux 不支持递归监听,因此对每个(非忽略的)目录各挂一个 inotify watch,代价是 O(目录数) 而非 O(文件数)。目录数上限默认 5 万,可用CODEGRAPH_MAX_DIR_WATCHES调整;触发fs.inotify.max_user_watches内核上限(表现为ENOSPC)时只告警并停止新增监听,已装的 watch 继续工作,警告信息会直接给出调高上限的sysctl命令。 - 自适应防抖快速路径:待处理文件数不超过 2 个时(一次单独的保存,或"编辑器文件 + 对应测试文件"这种成对出现),防抖窗口缩短为 300ms 的"快速静默窗"(下限 100ms),图谱几乎即时更新;更大的批量变更仍走完整防抖窗口,行为与之前完全一致。防抖语义始终是尾沿(trailing-edge)——每次新事件都会重置计时器。
同步失败的处理同样有界:写锁竞争(另一个进程持有数据库写锁)连续 5 次、或一般性同步失败(解析器崩溃、DB 损坏等确定性错误)连续 5 次后,监听器会永久降级(degrade)而不是无限重试刷日志,并通过回调把"自动同步已禁用,请手动跑 codegraph sync"这类可执行的原因报告给宿主进程;指数退避重试的封顶值为 30 秒。
第二层:按文件陈旧度横幅 —— 覆盖防抖窗口
防抖引入了一个约 2 秒的窗口:刚编辑过的文件已在磁盘上、但还没进索引。CodeGraph 用一个"按文件的陈旧度横幅"封住这个窗口——如果任何 MCP 工具响应引用了当前待重新索引的文件,响应开头就会追加一个 ⚠️ 横幅,点名这些陈旧文件:
⚠️ Some files referenced below were edited since the last index sync —
their codegraph entries may be stale:
- src/Widget.ts (edited 800ms ago, pending sync)
For accurate content of those specific files, Read them directly.
The rest of this response is fresh.
## Code Context
…
Agent 读到后会直接对点名的文件发起 Read 跟进——官方文档说这一点已经用 Claude Code 端到端验证过:Agent 会字面地说出 "Reading the file directly for the live content" 再去打开文件。也就是说,即使在 2 秒的防抖窗口内,Agent 也绝不会拿到静默的错误答案。
未被响应引用的待处理文件则改以一个小尾注形式呈现(Note: N file(s) elsewhere in this project are pending index sync but were not referenced above: …),保证新鲜度信号始终是显式的,不会悄悄丢失。
源码上,横幅与尾注的文案都由 tools.ts 中的两个纯函数生成:formatStaleBanner()(横幅,逐文件列出 edited <ms>ms ago 与 pending sync / indexing in progress 状态)和 formatStaleFooter()(尾注,最多列 5 个文件,超出部分折叠为一行 …and N more)。而"哪些文件是待处理的"来自 FileWatcher.getPendingFiles():每个待处理文件记录首次/最近一次事件时间,且条目只在同步成功提交后、且其最近事件早于同步开始时间时才被清除——同步过程中到达的新事件会继续挂起,留给下一轮同步。注释里写明了取舍偏好:宁可"误报陈旧"(代价至多是 Agent 多 Read 一次),不可"误报新鲜"(会让 Agent 基于过期索引给出错误答案)。
另有一个整索引级别的横幅值得知道:当实时监听永久停止(资源耗尽、锁竞争超过预算等),getPendingFiles() 为空、按文件横幅不会触发,此时读取类工具会改发 ⚠️ CodeGraph auto-sync is DISABLED 横幅,告知 Agent 整个索引已冻结、应直接 Read 文件确认——同样是"绝不静默"原则的延伸。
第三层:连接时追赶同步 —— 覆盖 MCP 服务未运行的空窗
当你的编辑器 / Agent 与 MCP 服务(重新)连接时,CodeGraph 会在回答第一个查询之前先跑一次快速的基于文件系统的对账:先用 (size, mtime) stat 预筛,再对其余文件做内容哈希比对。于是那些在"没有 MCP 服务运行"期间发生的变化——终端里的一次 git pull、从另一个编辑器进来的编辑、一个已经跑完退出的 Agent——都会在下一个会话的第一个工具调用中被自动追上。
源码上这个动作是 engine.ts 的 catchUpSync():在 open() 之后立即在后台执行 cg.sync(),并把返回的 Promise 交给工具处理层作为一个"一次性门闸"——第一个工具调用必须等它完成才返回。这一步的必要性在于:追赶同步不经过监听器,getPendingFiles() 里不会有它的影子,陈旧度横幅帮不上忙;没有门闸的话,抢先于同步完成的查询会返回"文件在磁盘上已不存在"的旧行。同步完成后若有变化,stderr 会打印 Caught up N file(s) changed since last run。
验证监听器看到了什么
codegraph_status 工具把待处理集合作为一等公民暴露出来——Agent 问"索引追上了吗"只需一次调用:
codegraph_status →
## CodeGraph Status
…
### Pending sync:
- src/Widget.ts (edited 1200ms ago)
如果响应里没有 Pending sync: 段,就说明当前没有任何同步在途。 对应实现见 tools.ts 的 handleStatus():它除了逐文件列出待处理项(含 edited <ms> ago 与 pending sync / indexing in progress 状态),还会报告文件 / 节点 / 边计数、数据库大小、当前 SQLite 后端与日志模式(journal mode 非 wal 时会警告"读取可能被并发写入阻塞")、被中断的解析残留(Pending resolution)以及"自动同步已禁用"段(若监听器永久降级,这里会明确说"索引已冻结,请直接 Read 文件")。
CLI 侧:
codegraph status
报告节点 / 边 / 文件计数、活动 SQLite 后端与日志模式;Agent 会话中的 MCP 侧 codegraph_status 额外给出上面描述的 Pending sync: 块。
什么时候才需要手动 codegraph sync
答案:几乎从不需要。只有两个边缘场景:
- 监听器被禁用时。沙箱环境会阻止本地文件监听,或者你设置了
CODEGRAPH_NO_DAEMON=1退出共享守护进程模式——这两种情况下codegraph sync是手动兜底。(监听禁用原因由 watch-policy.ts 统一判定,例如 WSL2 下/mnt/挂载盘上的fs.watch会长时间阻塞,会被主动跳过并提示手动同步或 git 钩子。) - CI 跑之前的预检。如果你是在 Agent 会话之外、通过脚本直接消费索引,在脚本开头跑一次
codegraph sync可以保证索引反映当前工作树。
除此之外:直接用就行。监听器 + 横幅 + 连接时同步已经端到端覆盖了 AI 辅助工作流。如果你在防抖窗口早已过去之后仍看到文件被真正漏掉,那是一个 bug——请带着复现步骤提 issue。
哪些文件会被索引
所有扩展名映射到支持语言的源码文件,减去以下排除项:
- 默认排除的依赖 / 构建目录(
node_modules、vendor、dist等); - 你
.gitignore排除的一切; - 超过 1 MB 的文件。
监听器与索引器共用同一套作用域匹配器(内置默认排除 + 项目 .gitignore + codegraph.json 的 include/exclude 规则),两者作用域永远一致;.codegraph/ 数据目录与 .git/ 无论如何都会忽略。完整的排除 / 包含配置与语言清单见官方文档站中的 Configuration 与 Supported Languages 页面。
小结
CodeGraph 的索引模型可以概括为一句话:init 一步建图,sync 只管增量,保鲜交给自动机制。三层机制各管一段窗口——监听器 + 防抖覆盖正常编辑流,按文件陈旧度横幅覆盖防抖窗口内的查询,连接时追赶同步覆盖服务未运行的空窗;codegraph_status 的 Pending sync: 块则是验证一切是否跟手的单一入口。若需进一步阅读源码,建议从 watcher.ts(监听与防抖)、engine.ts(MCP 侧启动、防抖参数解析、追赶同步门闸)与 tools.ts(状态输出与三类陈旧度横幅文案)三个文件切入,配套测试可参考 watcher.test.ts 与 mcp-staleness-banner.test.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00