Magic Docs:自主文档维护与同步
一句话讲清楚
Magic Docs 是 Claude Code 提供的一种“自生长文档”方案。用户只需在 Markdown 文件的首行添加一个特殊的头部标记(如 # MAGIC DOC: 系统架构),Claude Code 就会在后台持续追踪对话中的关键变更,并在交互间隙自动更新这些文档。
不同于 Auto Memory 这种分散在 .claude/memory 里的知识库,Magic Docs 通常是项目代码库中已有的、面向人类读者的文档文件(如 API.md, DEPLOY.md)。Magic Docs 旨在通过后台代理消除“代码已改,文档未动”的同步鸿沟。
实现机制
Magic Docs 的核心逻辑位于 src/services/MagicDocs/magicDocs.ts:
检测与注册机制 (Detection & Registration):
- 正则表达式触发:通过
MAGIC_DOC_HEADER_PATTERN检测文件首行。 - 指令提取:如果首行之后紧跟着一段斜体文字(如
*请侧重描述组件间的数据流*),detectMagicDocHeader会将其提取为instructions(额外指令),作为更新文档时的约束。 - 按需追踪:系统并不是遍历全盘寻找 Magic Docs,而是通过
registerFileReadListener在任何工具(如FileReadTool)读取文件时进行拦截。一旦发现标记,立即通过registerMagicDoc将其加入追踪列表(trackedMagicDocs)。
- 正则表达式触发:通过
触发时机与子代理 (Update Loop):
- Post-Sampling 钩子:Magic Docs 注册了一个
updateMagicDocs钩子,它在每次模型回复(Sampling)结束后执行。 - 空闲检测:为了不干扰主对话逻辑,
updateMagicDocs会检查hasToolCallsInLastAssistantTurn。只有在模型没有产生新的工具调用(即对话处于“空闲/确认”状态)时,才会启动更新。 - 专职代理:
getMagicDocsAgent定义了一个专门的代理。这个代理被严格限制只能使用FILE_EDIT工具,且只能操作对应的 Magic Doc 文件。
- Post-Sampling 钩子:Magic Docs 注册了一个
Prompt 构建与整合:
buildMagicDocsUpdatePrompt会将当前对话的完整上下文、文档的当前内容以及提取出的instructions喂给子代理,让子代理判断本次对话中是否有值得沉淀到该文档中的新知识。
别踩这些坑
- 权限沙盒:Magic Doc 子代理的
canUseTool实现(行 172-192)是一个极佳的安全示例。它通过动态闭包强制限定代理只能对特定filePath执行FILE_EDIT操作,任何对其它文件的写入尝试都会被拦截。 - Ant-Only 限制:目前版本的
initMagicDocs中包含了一个明显的限制:if (process.env.USER_TYPE === 'ant')。这意味着在开源版本中,这一特性可能需要手动开启或通过设置USER_TYPE=ant来激活其监听器。 - 存储位置:Magic Docs 是原位更新(In-place update)的。这意味着它直接修改你的源代码文件,因此建议在 Git 仓库中使用,以便随时对比和回滚。
继续探索
- 查看
src/services/MagicDocs/prompts.ts了解如何编写让模型能够精准识别文档更新点的提示词。 - 研究
src/utils/hooks/postSamplingHooks.ts了解 Claude Code 的各种生命周期钩子。
源码锚点
claude-code-opensource/src/services/MagicDocs/magicDocs.ts: 所有的检测、注册与更新循环实现。
📄 src/services/MagicDocs/magicDocs.ts — 所有的检测、注册与更新循环实现。
typescript
type REPLHookContext,
registerPostSamplingHook,
} from '../../utils/hooks/postSamplingHooks.js'
import {claude-code-opensource/src/tools/FileReadTool/FileReadTool.ts: 注册监听器的源头,负责 Magic Docs 的“初次发现”。
📄 src/tools/FileReadTool/FileReadTool.ts — 注册监听器的源头,负责 Magic Docs 的“初次发现”。
typescript
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
getFileExtensionForAnalytics,
} from '../../services/analytics/metadata.js'
import {claude-code-opensource/src/utils/fileStateCache.ts: 用于在更新文档前清理缓存,确保读取到的是最新内容。
📄 src/utils/fileStateCache.ts — 用于在更新文档前清理缓存,确保读取到的是最新内容。
typescript
export type FileState = {
content: string
timestamp: number
offset: number | undefined
limit: number | undefined
// True when this entry was populated by auto-injection (e.g. CLAUDE.md) and
// the injected content did not match disk (stripped HTML comments, stripped
// frontmatter, truncated MEMORY.md). The model has only seen a partial view;
// Edit/Write must require an explicit Read first. `content` here holds the
// RAW disk bytes (for getChangedFiles diffing), not what the model saw.
isPartialView?: boolean
}