一个直觉性的想法是:既然 Claude 要理解我的代码库,那就把所有代码都塞进上下文窗口不就好了?
答案是:不行。原因有三:
所以 Claude Code 采用了一种更聪明的策略:按需检索。不是把所有书都搬进房间,而是在需要时去图书馆找到那本书、翻到那一页。
Claude Code 理解代码库的核心能力建立在三个搜索工具之上。它们各有所长,组合使用就像一个经验丰富的开发者浏览代码库一样。
Glob 是文件路径的模式匹配工具。当你知道你要找什么类型的文件,但不确定它在哪里时,Glob 是第一选择。
// 查找所有 TypeScript 文件
Glob("**/*.ts")
→ src/index.ts, src/utils/helper.ts, src/services/user.ts, ...
// 查找 components 目录下所有 React 组件
Glob("src/components/**/*.tsx")
→ src/components/Button.tsx, src/components/Modal.tsx, ...
// 查找所有测试文件
Glob("**/*.test.{ts,tsx}")
→ src/__tests__/user.test.ts, src/components/Button.test.tsx, ...
// 查找配置文件
Glob("*config*")
→ tsconfig.json, tailwind.config.ts, vite.config.ts, ...Grep 是内容搜索工具,支持正则表达式。当你知道代码里有什么关键词,但不知道在哪个文件时,Grep 是利器。
// 查找某个类的定义
Grep("class UserService")
→ src/services/user-service.ts:15: export class UserService {
// 查找某个函数的所有调用位置
Grep("handleSubmit\(")
→ src/components/LoginForm.tsx:42: onClick={handleSubmit}
→ src/components/RegisterForm.tsx:38: onSubmit={handleSubmit}
// 查找 TODO 注释
Grep("TODO|FIXME|HACK")
→ src/utils/cache.ts:23: // TODO: implement cache invalidation
→ src/api/auth.ts:67: // FIXME: token refresh race conditionRead 是精确读取工具。当你已经知道要看哪个文件时,Read 让你读取全部或部分内容。
// 读取整个文件
Read("src/services/user-service.ts")
→ (返回文件全部内容)
// 只读取特定行范围(大文件时非常有用)
Read("src/services/user-service.ts", offset=100, limit=50)
→ (返回第 100-150 行)这三个工具形成了一个从广到窄的搜索管道:
传统的 RAG(检索增强生成)系统通常依赖向量嵌入:把代码块转换成向量,存入向量数据库,然后通过语义相似度搜索。但 Claude Code 不使用这种方法。
Claude Code 采用的是一种工具驱动的检索方式,我们称之为 “Agentic RAG”——AI 自己决定搜什么、怎么搜、搜多深。这不是预先建好索引然后查询,而是实时的、动态的、多步骤的探索。
让我们看一个完整的示例。当用户说 “帮我给 UserService 添加一个批量删除用户的方法” 时,Claude 的搜索过程是这样的:
用户: "帮我给 UserService 添加一个批量删除用户的方法"
Step 1: 理解任务,定位目标文件
Claude 思考: "我需要找到 UserService 的定义"
→ Grep("class UserService")
→ 结果: src/services/user-service.ts:15
Step 2: 阅读现有代码,理解模式
Claude 思考: "我需要了解现有方法的风格"
→ Read("src/services/user-service.ts")
→ 发现: 有 deleteUser(id) 方法,使用 Repository 模式
Step 3: 追踪依赖,理解数据层
Claude 思考: "deleteUser 调用了 UserRepository,我需要看看它的接口"
→ Grep("class UserRepository")
→ 结果: src/repositories/user-repository.ts:8
→ Read("src/repositories/user-repository.ts")
→ 发现: Repository 有 deleteById 方法,但没有批量删除
Step 4: 检查测试文件,了解测试规范
Claude 思考: "我应该看看现有测试的写法"
→ Glob("**/*user-service*.test.*")
→ 结果: src/__tests__/user-service.test.ts
→ Read("src/__tests__/user-service.test.ts")
→ 了解: 测试使用 Jest + mock Repository
Step 5: 综合所有信息,开始编码
Claude 现在拥有足够的上下文:
- UserService 的结构和风格
- Repository 的接口和模式
- 测试的编写规范
→ 编写 bulkDeleteUsers 方法
→ 在 Repository 中添加 deleteByIds 方法
→ 编写对应的测试注意这个过程的关键特征:
这就是 “Agentic” 的含义——AI 不是被动地接收检索结果,而是主动地策划和执行搜索策略。
文本搜索很强大,但它有盲点。当你搜索 handleClick 时,你可能找到定义、调用、注释中的提及、甚至字符串中的巧合匹配。你需要的是语义级别的理解——什么是定义,什么是引用,变量的类型是什么。
这就是 Language Server Protocol (LSP) 和代码智能工具发挥作用的地方。
LSP 提供的能力包括:
handleSubmit 定义在哪个文件。UserService 在哪些地方被使用?一个命令就能找到。在实践中,Claude Code 会将文本搜索和代码智能结合使用。文本搜索用于快速的广度扫描,代码智能用于精确的语义理解。两者互补,形成对代码库的全面认知。
即使有了按需检索,长时间的对话仍然会耗尽上下文窗口。每次工具调用的结果、每次读取的文件内容,都在消耗宝贵的上下文空间。当对话足够长时,Claude Code 会触发上下文压缩。
上下文压缩的工作方式:
上下文压缩就像人类做笔记:你不会记住对话中的每一个字,但你会记住关键结论和决策。这让 Claude Code 能够在长时间的编码会话中保持高效运转。
了解了工具之后,让我们看看 Claude Code 在实际场景中是如何组合运用这些搜索策略的。
这是最常见的模式。先用 Glob 获得全局视图,再用 Grep 精确定位,最后用 Read 深入理解。
// 任务: "重构项目中所有的 API 调用"
// 第一步: 广度扫描——找到所有可能相关的文件
Glob("src/**/*api*")
Glob("src/**/*service*")
Glob("src/**/*fetch*")
// 第二步: 精确定位——搜索具体的 API 调用模式
Grep("fetch\(|axios\.|api\.get|api\.post", "src/")
// 第三步: 深入理解——读取关键文件
Read("src/lib/api-client.ts") // API 客户端的封装
Read("src/services/user-api.ts") // 典型的 API 服务好的项目结构本身就是最好的文档。文件名和目录结构包含了丰富的语义信息。
// 要找数据库相关代码?
Glob("**/*database*")
Glob("**/*migration*")
Glob("**/prisma/**")
Glob("**/schema*")
// 要找认证相关代码?
Glob("**/*auth*")
Glob("**/*login*")
Glob("**/*session*")
Glob("**/middleware/*")代码之间的依赖关系形成了一张图。沿着 import 语句追踪,可以发现所有相关模块。
// 从入口点开始
Read("src/pages/dashboard.tsx")
→ 发现 import { UserCard } from '../components/UserCard'
Read("src/components/UserCard.tsx")
→ 发现 import { useUser } from '../hooks/useUser'
Read("src/hooks/useUser.ts")
→ 发现 import { UserService } from '../services/user-service'
// 现在你理解了整个依赖链:
// 页面 → 组件 → Hook → 服务测试文件是理解一个模块如何使用的最佳文档。它们包含了具体的输入输出示例和边界条件。
// 不确定某个函数怎么用?先找它的测试
Glob("**/*user-service*.test.*")
Read("src/__tests__/user-service.test.ts")
// 测试文件告诉你:
// - 函数的输入参数是什么
// - 预期的返回值是什么
// - 边界条件是什么
// - 错误情况如何处理这些策略不是孤立使用的——在一次真实的任务中,Claude 通常会灵活组合多种策略,就像一个有经验的开发者在新项目中快速建立认知一样。关键原则是:每一次搜索都有明确目的,每一次读取都在积累上下文。
An intuitive idea: since Claude needs to understand my codebase, why not just load all the code into the context window?
The answer is: you can’t. For three reasons:
So Claude Code employs a smarter strategy: on-demand retrieval. Instead of moving every book into the room, go to the library when needed, find that book, and turn to the right page.
Claude Code’s core ability to understand codebases is built on three search tools. Each has its strengths, and used together they work like an experienced developer navigating a codebase.
Glob is a file path pattern-matching tool. When you know what type of file you’re looking for but aren’t sure where it lives, Glob is your first choice.
// Find all TypeScript files
Glob("**/*.ts")
→ src/index.ts, src/utils/helper.ts, src/services/user.ts, ...
// Find all React components under components/
Glob("src/components/**/*.tsx")
→ src/components/Button.tsx, src/components/Modal.tsx, ...
// Find all test files
Glob("**/*.test.{ts,tsx}")
→ src/__tests__/user.test.ts, src/components/Button.test.tsx, ...
// Find config files
Glob("*config*")
→ tsconfig.json, tailwind.config.ts, vite.config.ts, ...Grep is a content search tool with full regex support. When you know a keyword exists in the code but don’t know which file, Grep is the weapon of choice.
// Find a class definition
Grep("class UserService")
→ src/services/user-service.ts:15: export class UserService {
// Find all call sites of a function
Grep("handleSubmit\(")
→ src/components/LoginForm.tsx:42: onClick={handleSubmit}
→ src/components/RegisterForm.tsx:38: onSubmit={handleSubmit}
// Find TODO comments
Grep("TODO|FIXME|HACK")
→ src/utils/cache.ts:23: // TODO: implement cache invalidation
→ src/api/auth.ts:67: // FIXME: token refresh race conditionRead is the precision tool. When you already know which file to examine, Read lets you view all or part of its contents.
// Read an entire file
Read("src/services/user-service.ts")
→ (returns full file contents)
// Read only a specific line range (useful for large files)
Read("src/services/user-service.ts", offset=100, limit=50)
→ (returns lines 100-150)These three tools form a broad-to-narrow search pipeline:
Traditional RAG (Retrieval-Augmented Generation) systems typically rely on vector embeddings: convert code chunks into vectors, store them in a vector database, then search by semantic similarity. Claude Code does not use this approach.
Instead, Claude Code employs a tool-driven retrieval approach we call “Agentic RAG” — the AI itself decides what to search for, how to search, and how deep to go. This is not pre-built indexing followed by queries, but real-time, dynamic, multi-step exploration.
Let’s walk through a complete example. When the user says “Add a bulk delete users method to UserService,” Claude’s search process looks like this:
User: "Add a bulk delete users method to UserService"
Step 1: Understand the task, locate the target file
Claude thinks: "I need to find the UserService definition"
→ Grep("class UserService")
→ Result: src/services/user-service.ts:15
Step 2: Read existing code, understand patterns
Claude thinks: "I need to understand the style of existing methods"
→ Read("src/services/user-service.ts")
→ Found: has a deleteUser(id) method, uses Repository pattern
Step 3: Trace dependencies, understand the data layer
Claude thinks: "deleteUser calls UserRepository, I need to check its interface"
→ Grep("class UserRepository")
→ Result: src/repositories/user-repository.ts:8
→ Read("src/repositories/user-repository.ts")
→ Found: Repository has deleteById method, but no bulk delete
Step 4: Check test files, learn testing conventions
Claude thinks: "I should look at how existing tests are written"
→ Glob("**/*user-service*.test.*")
→ Result: src/__tests__/user-service.test.ts
→ Read("src/__tests__/user-service.test.ts")
→ Learned: Tests use Jest + mock Repository
Step 5: Synthesize all information, start coding
Claude now has enough context:
- UserService structure and style
- Repository interface and patterns
- Testing conventions
→ Write bulkDeleteUsers method
→ Add deleteByIds to Repository
→ Write corresponding testsNote the key characteristics of this process:
This is what “Agentic” means — the AI is not passively receiving retrieval results, but actively planning and executing search strategies.
Text search is powerful, but it has blind spots. When you search for handleClick, you might find the definition, call sites, mentions in comments, or even coincidental matches in strings. What you need is semantic-level understanding — what is a definition, what is a reference, what is a variable’s type.
This is where Language Server Protocol (LSP) and code intelligence tools come in.
LSP provides capabilities including:
handleSubmit is defined in.UserService is referenced? One command finds them all.In practice, Claude Code combines text search and code intelligence. Text search handles fast, broad sweeps; code intelligence provides precise semantic understanding. They complement each other, forming a comprehensive picture of the codebase.
Even with on-demand retrieval, long conversations still exhaust the context window. Every tool call result, every file read, consumes precious context space. When the conversation gets long enough, Claude Code triggers Context Compaction.
How Context Compaction works:
Context Compaction is like a person taking notes: you don’t remember every word of a conversation, but you remember the key conclusions and decisions. This allows Claude Code to keep working efficiently through long coding sessions.
Now that we understand the tools, let’s look at how Claude Code combines these search strategies in real-world scenarios.
This is the most common pattern. Start with Glob for a global view, narrow down with Grep, then deep-dive with Read.
// Task: "Refactor all API calls in the project"
// Step 1: Broad sweep — find all potentially relevant files
Glob("src/**/*api*")
Glob("src/**/*service*")
Glob("src/**/*fetch*")
// Step 2: Precise targeting — search for specific API call patterns
Grep("fetch\(|axios\.|api\.get|api\.post", "src/")
// Step 3: Deep understanding — read key files
Read("src/lib/api-client.ts") // API client wrapper
Read("src/services/user-api.ts") // Typical API serviceGood project structure is the best documentation. File names and directory structures contain rich semantic information.
// Looking for database-related code?
Glob("**/*database*")
Glob("**/*migration*")
Glob("**/prisma/**")
Glob("**/schema*")
// Looking for auth-related code?
Glob("**/*auth*")
Glob("**/*login*")
Glob("**/*session*")
Glob("**/middleware/*")Dependencies between code form a graph. Following import statements reveals all related modules.
// Start from the entry point
Read("src/pages/dashboard.tsx")
→ Found: import { UserCard } from '../components/UserCard'
Read("src/components/UserCard.tsx")
→ Found: import { useUser } from '../hooks/useUser'
Read("src/hooks/useUser.ts")
→ Found: import { UserService } from '../services/user-service'
// Now you understand the entire dependency chain:
// Page → Component → Hook → ServiceTest files are the best documentation for understanding how a module is used. They contain concrete input/output examples and edge cases.
// Not sure how to use a function? Find its tests first
Glob("**/*user-service*.test.*")
Read("src/__tests__/user-service.test.ts")
// Test files tell you:
// - What the function's input parameters are
// - What the expected return values are
// - What the edge cases are
// - How errors are handledThese strategies are not used in isolation — in a real task, Claude typically combines multiple strategies flexibly, much like an experienced developer rapidly building understanding in a new project. The key principle is: every search has a clear purpose, every read accumulates context.