主题
AI CLI 工具 Q&A
Q1: Claude Code 等 CLI 工具的"插件市场"是如何实现的?
结论先行
Claude Code 等终端 AI 编程工具没有传统意义上的"插件市场"(如 VS Code Marketplace 那种集中式商店)。它们的扩展能力主要通过 MCP(Model Context Protocol)协议 实现——这是一个去中心化的、基于协议的插件体系。
核心区别在于:
- IDE 插件市场(如 VS Code):集中式商店 → 下载安装 → 加载到 IDE 运行时
- CLI 工具扩展(如 Claude Code):标准协议 → 自行启动/连接 MCP Server → 工具注入到 Agent 上下文
1. 传统 IDE 插件市场的实现方式(参照对比)
以 VS Code 为代表的传统插件市场,架构如下:
开发者 → 打包 .vsix → 上传 Marketplace → 用户搜索/安装 → 宿主进程加载插件 DLL/JS核心组件:
- 注册表/商店:集中式服务器,托管插件元数据、版本、下载包
- CLI 安装命令:
code --install-extension <id> - 运行时加载:宿主进程在启动时扫描已安装插件目录,加载到扩展宿主进程中
- API Surface:插件通过宿主提供的 API(
vscode.*)与编辑器交互
这种模式的前提是:有一个长期运行的 GUI 宿主进程,插件作为宿主的子模块运行。
2. CLI 工具为什么不走传统插件市场路线?
CLI 工具的运行模型与 IDE 有本质区别:
| 维度 | IDE(VS Code) | CLI(Claude Code) |
|---|---|---|
| 生命周期 | 长期运行,常驻后台 | 按需启动,用完退出 |
| 运行时 | 有完整的 Extension Host 进程 | 一个简单的 Node.js/Python 进程 |
| UI 扩展 | 需要与编辑器 UI 深度集成 | 无 GUI,不需要 UI 扩展点 |
| 核心能力 | 语法高亮、智能提示、调试器 | 工具调用(文件/Shell/搜索) |
| 扩展诉求 | 添加语言支持、UI 面板、调试器 | 添加外部数据源和操作能力 |
CLI 工具的扩展需求非常聚焦:给 AI 模型增加新的"工具"——能调用的 API、能查询的数据源、能执行的操作。不需要 UI 渲染、不需要语法高亮、不需要调试协议。
这种轻量的需求,用一个标准通信协议就能解决,不需要复杂的插件加载机制。
3. MCP:CLI 工具的"插件协议"
3.1 MCP 是什么
MCP(Model Context Protocol)是 Anthropic 于 2024 年底推出的开放协议,定位是 "AI 应用的 USB-C 接口"——一个连接 AI 模型与外部工具/数据的标准化协议。
┌─────────────┐ ┌─────────────────┐
│ AI CLI 工具 │ MCP 协议 │ MCP Server │
│ (MCP Client)│◄═══════════════════► │ (插件/工具提供者) │
│ │ JSON-RPC over │ │
│ Claude Code │ stdio / HTTP │ GitHub / Jira │
│ Cursor │ │ DB / Sentry │
│ Windsurf │ │ 自定义服务... │
└─────────────┘ └─────────────────┘3.2 MCP 的"插件"发现与安装
MCP 没有集中式商店,但有多种发现机制:
"插件发现"途径:
1. MCP 注册表网站
├── mcp.so ← 社区驱动的 MCP Server 目录
├── smithery.ai ← MCP 工具注册表
├── glama.ai/mcp/servers ← 另一个聚合目录
└── github.com/modelcontextprotocol/servers ← 官方参考实现
2. npm / PyPI 包
└── npm install @modelcontextprotocol/server-github
└── pip install mcp-server-sqlite
3. Docker 镜像
└── docker run mcp/server-postgres
4. 自建 MCP Server
└── 用任意语言实现 MCP 协议即可3.3 "安装插件" = 配置 MCP Server
在 Claude Code 中,"安装一个插件"本质上就是在配置文件中声明一个 MCP Server:
json
// .claude/settings.json 或 ~/.claude/settings.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://..."
}
},
"my-company-api": {
"type": "http",
"url": "https://mcp.mycompany.com/sse"
}
}
}启动时,Claude Code 会:
- 读取配置,启动/连接每个 MCP Server 进程
- 通过 MCP 协议发现该 Server 提供的工具列表(
tools/list) - 将这些工具的 JSON Schema 注入到系统提示中
- 模型即可像使用内建工具一样调用这些外部工具
3.4 MCP 协议核心交互
CLI 启动
│
├── 1. initialize(握手)
│ Client → Server: 协议版本、能力声明
│ Server → Client: 服务器能力、支持的功能
│
├── 2. tools/list(发现工具)
│ Server → Client: [
│ { name: "create_issue", description: "...", inputSchema: {...} },
│ { name: "search_code", description: "...", inputSchema: {...} }
│ ]
│
├── 3. tools/call(调用工具)——由模型决策触发
│ Client → Server: { name: "create_issue", arguments: { title: "Bug fix", body: "..." } }
│ Server → Client: { content: [{ type: "text", text: "Issue #42 created" }] }
│
└── 4. resources/list + resources/read(可选:数据资源)
Server → Client: 提供可读取的数据资源列表通信方式:
- stdio:CLI 启动子进程,通过标准输入/输出通信(本地插件首选)
- HTTP + SSE:通过网络连接远程 MCP Server(远程服务首选)
4. 对比:三种"扩展"实现模式
模式一:传统插件市场(VS Code)
┌────────┐ 下载 ┌────────┐ 加载 ┌──────────┐
│ 商店 │ ────────► │ 本地包 │ ────────► │ 宿主进程 │
│ .vsix │ │ 文件 │ │ 扩展 API │
└────────┘ └────────┘ └──────────┘
模式二:MCP 协议(Claude Code)
┌──────────┐ 配置 ┌────────────┐ 协议通信 ┌──────────┐
│ 注册表 │ ───────► │ 配置文件 │ ──────────► │ MCP │
│ npm/目录 │ │ settings │ JSON-RPC │ Server │
└──────────┘ └────────────┘ └──────────┘
模式三:内建 SDK 扩展(Claude Agent SDK)
┌──────────┐ npm ┌────────────┐ 编程调用 ┌──────────┐
│ npm │ ───────► │ 项目代码 │ ──────────► │ 自定义 │
│ 发布 │ │ import SDK │ tool() │ Agent │
└──────────┘ └────────────┘ └──────────┘5. 不同 CLI 工具的扩展策略对比
| 工具 | 主要扩展机制 | 是否有集中商店 | 扩展入口 |
|---|---|---|---|
| Claude Code | MCP + Hooks + SDK | 无(依赖 MCP 注册表网站) | .claude/settings.json |
| Cursor | MCP + VS Code 扩展市场 | 是(继承 VS Code Marketplace) | Settings → MCP |
| GitHub Copilot CLI | 内建能力为主 | 否 | 极少扩展点 |
| Aider | 配置文件 + LiteLLM | 否 | .aider.conf.yml |
| Continue.dev | MCP + 自定义 Provider | 社区 Hub | config.json |
6. 为什么 MCP 模式更适合 AI CLI 工具?
1. 语言无关 传统插件必须用宿主语言(VS Code 用 JS/TS)。MCP Server 可以用任何语言写——Python、Go、Rust、Java 都行,只要实现 JSON-RPC 协议。
2. 进程隔离 每个 MCP Server 是独立进程,崩溃不会影响主 CLI。传统插件跑在宿主进程里,一个插件 crash 可能拖垮整个编辑器。
3. 复用性高 一个 MCP Server 可以同时被 Claude Code、Cursor、Windsurf 等多个客户端使用。传统插件只能用于特定 IDE。
4. 部署灵活
- stdio 模式:本地进程,零网络延迟
- HTTP 模式:远程部署,团队共享,支持认证
5. 安全边界清晰 MCP Server 只能通过协议暴露的工具/资源与 CLI 交互,无法直接访问 CLI 内部状态。相比之下,传统插件可以调用宿主的任意 API。
7. MCP 生态的当前状态
截至 2025 年末,MCP 生态:
- 3000+ MCP Server 已发布(官方统计)
- 主要客户端:Claude Code、Claude Desktop、Cursor、Windsurf、Cline、Continue 等
- 官方参考实现:GitHub、GitLab、Slack、Google Drive、PostgreSQL、SQLite、Sentry、Puppeteer 等
- 社区注册表:mcp.so、smithery.ai、glama.ai 等聚合站点(类似非官方"插件市场")
- 标准化进程:MCP 协议已开源,由 Anthropic 主导,多家公司参与
8. 总结
传统 IDE 插件市场 AI CLI 工具扩展
═══════════════ ═══════════════
集中式商店 去中心化注册表
↓ ↓
下载 .vsix 插件包 npm install / docker pull / 远程 URL
↓ ↓
宿主进程加载 启动独立 MCP Server 进程
↓ ↓
调用宿主 API 通过 JSON-RPC 协议通信
↓ ↓
仅限特定 IDE 跨客户端复用
核心差异:从"代码加载"变成"协议通信"AI CLI 工具的"插件市场"本质上是从代码级集成演变为协议级集成。MCP 协议扮演了"USB-C 接口"的角色——定义了标准的通信格式,让任何工具/服务都能以统一的方式被 AI 模型调用。这种模式更轻量、更安全、更具互操作性,非常契合 CLI 工具"轻量启动、按需连接"的运行特点。
Q2: Claude Code 输入 / 后弹出的所有斜杠命令详解
在 Claude Code 交互模式中,输入 / 会弹出命令列表,输入更多字母可过滤。截至 2026 年初,Claude Code 内建 40+ 个斜杠命令,按功能分为以下几大类。
1. 会话管理(Session Management)
控制对话的生命周期——新建、恢复、分叉、压缩、回退。
| 命令 | 别名 | 作用 |
|---|---|---|
/clear | /reset, /new | 清空对话历史,从零开始新会话 |
/compact [指令] | — | 压缩对话以释放上下文空间。可指定保留重点:/compact 保留 API 设计决策 |
/resume [会话] | /continue | 恢复之前的会话(按 ID/名称),不带参数则弹出会话选择器 |
/fork [名称] | — | 从当前对话分叉出一个新会话(保留当前历史,继续独立发展) |
/rename [名称] | — | 重命名当前会话。不带参数时自动根据对话内容生成名称 |
/rewind | /checkpoint | 回退代码和对话到之前的某个检查点(撤销 Claude 的操作) |
/exit | /quit | 退出 Claude Code |
使用策略:
/compact:上下文用量超过 80% 但还在同一个任务时使用,加焦点指令控制保留内容/clear:彻底切换到不同任务时使用,旧上下文只会干扰/resume:隔天继续昨天的工作时使用
2. 信息与诊断(Information & Diagnostics)
查看当前会话的状态——费用、上下文、健康度。
| 命令 | 作用 |
|---|---|
/cost | 显示当前会话的 token 用量和费用 |
/usage | 显示订阅计划的用量限制和速率限制状态 |
/context | 以彩色方格图可视化上下文窗口使用情况 |
/status | 显示版本号、当前模型、账户信息、连接状态 |
/doctor | 运行诊断检查(环境、依赖、配置是否正常) |
/help | 列出所有可用命令 |
/stats | 可视化每日使用量、会话历史、连续使用天数 |
/diff | 打开交互式 diff 查看器,显示未提交的更改和每轮 diff |
/export [文件名] | 将对话导出为纯文本文件或复制到剪贴板 |
/copy | 复制上一条 Claude 回复到剪贴板(有代码块时弹出选择器) |
/release-notes | 查看完整的版本更新日志 |
/insights | 生成分析报告,总结你的 Claude Code 使用模式 |
实用技巧:每 15-20 分钟跑一次 /cost,长会话中 token 消耗可能比预期快很多。
3. 模型与模式控制(Model & Mode Control)
切换模型、切换模式、调整 Claude 的行为方式。
| 命令 | 作用 |
|---|---|
/model [模型名] | 切换模型(sonnet/opus 或完整模型名),用左右方向键调整 effort 级别 |
/fast [on|off] | 切换快速模式(同一模型,更快输出) |
/plan | 进入 Plan 模式——Claude 先提出方案,确认后才执行 |
/vim | 切换 Vim 编辑模式和普通编辑模式 |
/output-style [风格] | 切换输出风格:Default / Explanatory / Learning |
/theme | 更换颜色主题(浅色、深色、色盲友好等选项) |
模型切换策略:大多数任务用 Sonnet(便宜快速),遇到复杂架构/疑难 bug 时切 Opus,难题解决后切回 Sonnet。
4. 配置与权限(Configuration & Permissions)
管理设置、权限、认证、项目初始化。
| 命令 | 别名 | 作用 |
|---|---|---|
/config | /settings | 打开设置界面 |
/permissions | /allowed-tools | 查看/修改工具权限(允许/拒绝列表) |
/init | — | 初始化项目,创建 CLAUDE.md 文件 |
/memory | — | 编辑 CLAUDE.md 记忆文件,开关自动记忆功能 |
/login | — | 登录 Anthropic 账户 |
/logout | — | 登出 Anthropic 账户 |
/hooks | — | 配置和管理生命周期 Hooks |
/agents | — | 管理子智能体配置 |
/skills | — | 列出可用的 Skills(自定义命令) |
/mcp | — | 管理 MCP Server 连接 |
/plugin | — | 管理 Claude Code 插件 |
/terminal-setup | — | 配置终端快捷键绑定(如 Shift+Enter) |
/keybindings | — | 打开或创建键绑定配置 |
/sandbox | — | 开关沙箱模式(隔离执行环境) |
/extra-usage | — | 配置速率限制触发时的额外用量 |
/privacy-settings | — | 查看/更新隐私设置(Pro/Max 用户) |
/statusline | — | 配置终端状态栏显示内容 |
5. 代码审查与 PR 工作流(Code Review & PR)
与 GitHub PR 交互,需要安装并认证 gh CLI。
| 命令 | 作用 |
|---|---|
/review [PR号] | 审查 Pull Request 的质量、正确性和安全性。不带参数列出所有 open PR |
/pr-comments [PR] | 获取并展示 GitHub PR 的评论,自动检测当前分支的 PR |
/security-review | 对未提交的更改进行安全漏洞分析 |
/install-github-app | 安装 Claude GitHub App,启用自动 PR 审查 |
6. 工作目录与集成(Working Directories & Integration)
| 命令 | 别名 | 作用 |
|---|---|---|
/add-dir <路径> | — | 向当前会话添加额外的工作目录 |
/ide | — | 管理 IDE 集成,查看连接状态 |
/chrome | — | 配置 Chrome 浏览器集成 |
/remote-control | /rc | 让当前会话可从 claude.ai 网页远程控制 |
/desktop | /app | 将当前会话转移到 Claude Code Desktop 应用继续 |
/tasks | — | 列出和管理后台任务 |
/feedback [内容] | /bug | 提交反馈或 Bug 报告 |
7. 快速参考卡片
日常高频命令 Top 10:
/cost ← 查费用(养成习惯,每 15 分钟看一次)
/context ← 查上下文剩余空间
/compact ← 压缩上下文(比 /clear 更温和)
/clear ← 彻底清空,开新会话
/model sonnet ← 切便宜模型
/model opus ← 切强模型(处理难题时)
/resume ← 恢复昨天的会话
/diff ← 看 Claude 改了什么
/doctor ← 出问题时先跑诊断
/help ← 忘了命令时看这个输入前缀快捷方式(不需要输入 /):
| 前缀 | 作用 | 示例 |
|---|---|---|
/ | 斜杠命令或 Skill | /compact 保留错误处理模式 |
! | 直接执行 Shell 命令(输出加入对话上下文) | ! git status |
@ | 文件路径自动补全 | @src/main.ts |
! 前缀非常实用——直接执行命令并把输出注入到对话上下文中,不需要 Claude 审批:
bash
! npm test # 跑测试,结果进入上下文
! git log --oneline -5 # 查最近 5 次提交
! cat .env.example # 查看文件内容8. 键盘快捷键
除了斜杠命令,Claude Code 还有一套键盘快捷键:
必记快捷键:
| 快捷键 | 作用 |
|---|---|
Escape | 取消当前生成(Claude 走错方向时立即打断) |
Escape × 2 | 回退/撤销 Claude 的上一步操作 |
Ctrl+C × 2 | 退出会话 |
Ctrl+R | 反向搜索历史输入 |
Ctrl+T | 显示/隐藏任务列表 |
Shift+Tab | 循环切换权限模式(Auto-Accept / Plan / 默认) |
Ctrl+O | 切换详细输出(查看完整工具调用过程) |
Ctrl+B | 将当前运行的命令放到后台 |
Alt+P | 不清空输入框直接切换模型 |
Alt+T | 开关扩展思考(Extended Thinking) |
Ctrl+G | 用外部编辑器打开当前输入(写长提示时很有用) |
多行输入方式(因为 Enter 默认是发送):
| 方法 | 快捷键 | 说明 |
|---|---|---|
| 反斜杠转义 | \ + Enter | 所有终端通用 |
| macOS 默认 | Option+Enter | macOS 默认方式 |
| Shift+Enter | Shift+Enter | iTerm2/WezTerm/Ghostty/Kitty 原生支持 |
| 换行符 | Ctrl+J | 所有终端通用 |
| 粘贴 | 直接粘贴多行文本 | 适合粘贴代码块和日志 |
Q3: 详细介绍一下 Claude Code 的 /plugin 命令与插件系统
1. 概述
/plugin 是 Claude Code v1.0.33+ 引入的插件管理命令。插件(Plugin)是 Claude Code 的一等扩展单元,它把 Skills、Agents、Hooks、MCP Server、LSP Server 打包成一个可安装、可分享、可版本化的独立目录。
与 Q1 中介绍的"MCP 协议级扩展"不同,Plugin 是更上层的打包和分发机制——一个 Plugin 内部可以同时包含 MCP Server、自定义命令、子智能体、生命周期钩子等多种组件。
截至 2026 年初,已有 9000+ 个插件可用。
2. Plugin vs 独立配置(何时用插件 vs .claude/ 目录)
| 维度 | 独立配置(.claude/ 目录) | 插件(Plugin) |
|---|---|---|
| 命令命名 | /hello | /plugin-name:hello(有命名空间) |
| 适用场景 | 单项目个人定制、快速实验 | 团队分享、社区发布、跨项目复用 |
| 版本管理 | 无 | 语义化版本(semver) |
| 分发方式 | 手动复制 | 通过 Marketplace 安装 |
| 冲突风险 | 有(同名覆盖) | 无(命名空间隔离) |
建议路径:先在 .claude/ 中快速迭代,成熟后转为 Plugin 分发。
3. 插件目录结构
一个完整的 Plugin 长这样:
my-plugin/
├── .claude-plugin/ ← 元数据目录(仅放 plugin.json)
│ └── plugin.json ← 插件清单(名称、版本、描述)
├── commands/ ← 斜杠命令(Markdown 文件)
│ ├── status.md
│ └── logs.md
├── skills/ ← Agent Skills(SKILL.md 结构)
│ ├── code-reviewer/
│ │ └── SKILL.md
│ └── pdf-processor/
│ ├── SKILL.md
│ └── scripts/
├── agents/ ← 自定义子智能体
│ ├── security-reviewer.md
│ └── performance-tester.md
├── hooks/ ← 生命周期钩子
│ └── hooks.json
├── output-styles/ ← 输出风格定义
│ └── terse.md
├── settings.json ← 启用时应用的默认设置
├── .mcp.json ← MCP Server 配置
├── .lsp.json ← LSP Server 配置(代码智能)
├── scripts/ ← 钩子脚本
│ ├── security-scan.sh
│ └── format-code.py
├── LICENSE
└── CHANGELOG.md关键规则:commands/、agents/、skills/、hooks/ 等目录必须放在插件根目录,不要放进 .claude-plugin/ 内部。.claude-plugin/ 里只放 plugin.json。
4. plugin.json 清单文件
json
{
"name": "my-plugin",
"version": "1.2.0",
"description": "我的自定义插件",
"author": {
"name": "Your Name",
"email": "you@example.com"
},
"homepage": "https://docs.example.com/plugin",
"repository": "https://github.com/user/plugin",
"license": "MIT",
"keywords": ["deployment", "ci-cd"],
"commands": ["./custom/commands/"],
"agents": "./custom/agents/",
"skills": "./custom/skills/",
"hooks": "./config/hooks.json",
"mcpServers": "./mcp-config.json",
"lspServers": "./.lsp.json",
"userConfig": {
"api_endpoint": {
"description": "你的 API 端点",
"sensitive": false
},
"api_token": {
"description": "API 认证令牌",
"sensitive": true
}
}
}| 字段 | 必选 | 说明 |
|---|---|---|
name | 是 | 唯一标识符(kebab-case),同时作为命名空间前缀 |
version | 否 | 语义化版本号(MAJOR.MINOR.PATCH) |
description | 否 | 插件说明,在插件管理器中显示 |
author | 否 | 作者信息 |
userConfig | 否 | 用户配置项,启用插件时提示用户输入(如 API Key) |
commands/agents/skills | 否 | 组件路径,不指定则使用默认目录 |
userConfig 中的值可以通过 ${user_config.KEY} 在 MCP/LSP/Hook 配置中引用,敏感值存入系统钥匙链。
5. 插件的六大组件
5.1 Skills(技能)
放在 skills/ 目录下,每个 Skill 是一个含 SKILL.md 的文件夹:
markdown
---
name: code-review
description: 审查代码质量和潜在问题。当用户提到 review、审查、检查代码时触发。
---
审查代码时检查以下方面:
1. 代码组织与结构
2. 错误处理
3. 安全隐患
4. 测试覆盖率安装后通过 /plugin-name:code-review 手动调用,或由 Claude 根据任务上下文自动触发。
5.2 Agents(子智能体)
放在 agents/ 目录下,Markdown 文件定义:
markdown
---
name: security-reviewer
description: 安全审查专家,分析代码中的安全漏洞
model: opus
effort: medium
maxTurns: 20
disallowedTools: Write, Edit
---
你是一名安全审查专家。分析代码中的注入、认证、数据暴露风险。支持的 frontmatter 字段:name、description、model、effort、maxTurns、tools、disallowedTools、skills、memory、background、isolation(仅支持 "worktree")。
5.3 Hooks(生命周期钩子)
放在 hooks/hooks.json,响应 Claude Code 的 25+ 个生命周期事件:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format-code.sh"
}]
}
]
}
}主要事件包括:
| 事件 | 触发时机 |
|---|---|
SessionStart | 会话开始或恢复 |
UserPromptSubmit | 用户提交提示词时 |
PreToolUse | 工具调用前(可阻止) |
PostToolUse | 工具调用成功后 |
SubagentStart/Stop | 子智能体启停时 |
FileChanged | 文件变更时 |
PreCompact/PostCompact | 上下文压缩前后 |
WorktreeCreate/Remove | Worktree 创建/移除时 |
SessionEnd | 会话结束时 |
Hook 类型:command(执行脚本)、http(发 POST 请求)、prompt(LLM 评估)、agent(启动子智能体验证)。
5.4 MCP Server
在 .mcp.json 中配置:
json
{
"mcpServers": {
"plugin-database": {
"command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"]
}
}
}插件启用时 MCP Server 自动启动,工具无缝集成到 Claude 的工具列表中。
5.5 LSP Server(代码智能)
在 .lsp.json 中配置,提供实时诊断、跳转定义、查找引用等能力:
json
{
"go": {
"command": "gopls",
"args": ["serve"],
"extensionToLanguage": { ".go": "go" }
}
}官方已提供的 LSP 插件:pyright-lsp(Python)、typescript-lsp(TypeScript)、rust-lsp(Rust)。
5.6 Settings(默认设置)
settings.json 可在启用时激活插件的自定义 Agent 作为主线程:
json
{
"agent": "security-reviewer"
}6. /plugin 交互式命令
在 Claude Code 交互模式中输入 /plugin,会打开插件管理界面,包含以下功能 Tab:
| Tab | 功能 |
|---|---|
| Discover | 浏览已配置的 Marketplace 中的可用插件 |
| Installed | 查看已安装的插件列表,启用/禁用/卸载 |
| Errors | 查看插件加载错误(如 LSP 找不到二进制文件) |
| Validate | 验证插件的 plugin.json、frontmatter、hooks 语法 |
7. CLI 命令行管理
除了交互式 /plugin,还可以在命令行中非交互式管理插件:
bash
# 安装插件(从 Marketplace)
claude plugin install formatter@my-marketplace
# 安装到项目作用域(团队共享,写入 .claude/settings.json)
claude plugin install formatter@my-marketplace --scope project
# 安装到本地作用域(gitignore,仅自己可见)
claude plugin install formatter@my-marketplace --scope local
# 卸载插件
claude plugin uninstall formatter@my-marketplace
# 卸载但保留持久数据
claude plugin uninstall formatter@my-marketplace --keep-data
# 启用/禁用(不卸载)
claude plugin enable my-plugin
claude plugin disable my-plugin
# 更新到最新版本
claude plugin update my-plugin安装作用域
| 作用域 | 配置文件 | 用途 |
|---|---|---|
user | ~/.claude/settings.json | 个人全局插件(默认) |
project | .claude/settings.json | 团队共享(提交到 Git) |
local | .claude/settings.local.json | 项目级私有(gitignore) |
managed | 托管配置 | 企业 IT 强制(只读,仅可更新) |
8. 开发与调试插件
本地测试
用 --plugin-dir 加载本地插件,无需安装:
bash
# 加载单个插件
claude --plugin-dir ./my-plugin
# 加载多个插件
claude --plugin-dir ./plugin-a --plugin-dir ./plugin-b修改插件后,在会话内运行 /reload-plugins 即可热重载,无需重启。
调试
bash
# 查看插件加载详情
claude --debug
# 在交互模式中验证插件
/plugin validate常见问题排查
| 问题 | 原因 | 解决 |
|---|---|---|
| 插件不加载 | plugin.json 语法错误 | 运行 /plugin validate 检查 |
| 命令不显示 | 目录结构错误 | 确保 commands/ 在插件根目录,不在 .claude-plugin/ 里 |
| Hook 不触发 | 脚本没有执行权限 | chmod +x script.sh |
| MCP Server 启动失败 | 路径用了绝对路径 | 改用 ${CLAUDE_PLUGIN_ROOT} 变量 |
LSP 报 Executable not found | 语言服务器未安装 | 先安装二进制(如 npm install -g typescript-language-server) |
9. 环境变量
插件中可以使用两个内建变量:
| 变量 | 说明 |
|---|---|
${CLAUDE_PLUGIN_ROOT} | 插件安装目录的绝对路径(插件更新后会变) |
${CLAUDE_PLUGIN_DATA} | 插件持久数据目录(~/.claude/plugins/data/{id}/),跨版本存活 |
典型用法——在 SessionStart 时自动安装依赖:
json
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install)"
}]
}]
}
}10. 插件分发与 Marketplace
插件通过 Marketplace(插件市场)分发。Marketplace 本身也是一个目录结构(可以是 Git 仓库、本地目录或远程 URL),包含一个 marketplace.json 索引文件。
发布到官方市场:
- Claude.ai:claude.ai/settings/plugins/submit
- Console:platform.claude.com/plugins/submit
安装流程:
用户 /plugin → Discover Tab → 浏览 Marketplace → 选择插件 → Install
↓
claude plugin install formatter@official
↓
插件被复制到 ~/.claude/plugins/cache/(安全隔离)
↓
组件自动注册(Skills、Agents、Hooks、MCP/LSP Server)
↓
用户可通过 /plugin-name:skill-name 调用安全机制:安装的插件会被复制到本地缓存(~/.claude/plugins/cache/),而非直接引用原路径,防止插件源被篡改。
11. 总结:Plugin 在 Claude Code 扩展体系中的位置
Claude Code 扩展能力全景:
┌─────────────────────────────────────────────────────┐
│ Plugin(插件)—— 一等打包单元 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
│ │ Skills │ │ Agents │ │ Hooks │ │ MCP/LSP │ │
│ │ 自定义 │ │ 子智能体 │ │ 生命周期 │ │ Server │ │
│ │ 命令 │ │ │ │ 钩子 │ │ 外部工具 │ │
│ └─────────┘ └─────────┘ └─────────┘ └───────────┘ │
├─────────────────────────────────────────────────────┤
│ 通过 /plugin 或 CLI 管理 │
│ 通过 Marketplace 分发 │
│ 通过 --plugin-dir 本地开发测试 │
└─────────────────────────────────────────────────────┘Plugin 是 Claude Code 的最高层扩展抽象——它不是某一种能力的扩展,而是把所有扩展能力(Skills + Agents + Hooks + MCP + LSP + Settings)打包成一个可安装、可分享、可版本化的标准单元。这使得 Claude Code 的扩展生态从"手动配置若干 MCP Server"演进为"一键安装一个功能完整的插件包"。
Q4: Plugin 中定义的 Agent,是如何与 MCP、Skill、Hook 等组件交互的?
1. 先理解 Agent 的本质
Agent(子智能体)本质上是一个带独立上下文窗口、独立系统提示、独立工具集的 Claude 实例。它不是一段代码,而是一个配置文件——用 YAML frontmatter 声明"我能用什么工具、什么模型、什么权限",用 Markdown 正文定义"我的系统提示是什么"。
markdown
---
name: browser-tester
description: 使用浏览器测试前端功能
model: sonnet
tools: Read, Bash, Grep
mcpServers:
- playwright:
type: stdio
command: npx
args: ["-y", "@playwright/mcp@latest"]
- github
skills:
- api-conventions
- error-handling-patterns
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-command.sh"
memory: project
isolation: worktree
background: false
maxTurns: 20
---
你是一名前端测试专家。使用 Playwright 工具对页面进行导航、截图和交互测试。上面这个 Agent 定义同时涉及了 MCP、Skills、Hooks、Memory、Worktree 隔离——下面逐一解释它们之间的交互关系。
2. 全景交互架构
主对话(Main Conversation)
│
│ ① Claude 根据 description 决定委派任务
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Agent: browser-tester │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 独立上下文窗口 │ │
│ │ │ │
│ │ 系统提示 = frontmatter 定义 + Markdown 正文 │ │
│ │ (不继承主对话的系统提示) │ │
│ │ │ │
│ │ ② skills 内容被注入 │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ api-conventions │ │ error-handling-patterns │ │ │
│ │ │ (全文注入上下文) │ │ (全文注入上下文) │ │ │
│ │ └─────────────────┘ └─────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ③ 可用工具集 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 内建工具: Read, Bash, Grep (由 tools 字段指定) │ │
│ │ MCP 工具: playwright.navigate, playwright.screenshot │ │
│ │ github.create_issue, github.list_prs │ │
│ │ (由 mcpServers 字段控制) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ④ Hooks 守卫 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PreToolUse("Bash") → validate-command.sh │ │
│ │ PostToolUse("Edit|Write") → run-linter.sh │ │
│ │ (仅在此 Agent 活跃时运行,Agent 结束后清理) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ⑤ 持久记忆 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ .claude/agent-memory/browser-tester/MEMORY.md │ │
│ │ (跨会话累积知识,启动时前 200 行注入上下文) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ⑥ Worktree 隔离 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 独立 git worktree → 独立文件目录 → 不影响主工作区 │ │
│ │ Agent 结束无修改时自动清理 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ⑦ 结果摘要返回主对话 │
└─────────────────────────────────────────────────────────────┘3. 各组件与 Agent 的交互机制详解
3.1 Agent 与 MCP Server 的交互
通过 mcpServers frontmatter 字段配置。有两种方式:
方式一:内联定义(Agent 独占)
yaml
mcpServers:
- playwright:
type: stdio
command: npx
args: ["-y", "@playwright/mcp@latest"]- MCP Server 在 Agent 启动时连接,Agent 结束时断开
- 主对话看不到这些工具(不占用主对话的上下文)
- 适合:Agent 专用的外部工具
方式二:引用已有 Server(共享连接)
yaml
mcpServers:
- github- 复用主会话已配置的 MCP Server 连接
- Agent 和主对话共享同一个连接实例
交互流程:
Agent 启动
│
├── 连接内联定义的 MCP Server(playwright)
├── 共享已有的 MCP Server 连接(github)
│
├── MCP tools/list → 发现工具列表
│ → playwright.navigate, playwright.screenshot...
│ → github.create_issue, github.list_prs...
│
├── 工具的 JSON Schema 注入 Agent 的上下文
│
├── Agent(Claude 模型)决策调用某工具
│ → tools/call → MCP Server 执行 → 返回结果
│
└── Agent 结束
→ 断开内联 MCP Server
→ 保留共享 Server 连接关键设计:把 MCP Server 定义在 Agent 的 mcpServers 而非全局 .mcp.json 中,可以避免工具描述污染主对话上下文。如果你有一个工具很重要但描述很长(如 Playwright 有几十个工具),放在 Agent 里可以节省主对话的 token。
3.2 Agent 与 Skills 的交互
通过 skills frontmatter 字段配置:
yaml
skills:
- api-conventions
- error-handling-patterns关键行为:
| 特性 | 说明 |
|---|---|
| 注入方式 | Skill 的完整内容被注入到 Agent 的系统提示中 |
| 不是"可调用" | 不是"Agent 可以调用这个 Skill",而是"Skill 内容直接成为 Agent 知识的一部分" |
| 不继承 | Agent 不会继承主对话的 Skills,必须显式列出 |
| 命名空间 | 插件 Skill 需要用完整名称:plugin-name:skill-name |
这意味着 Skill 对 Agent 来说是编译时注入,不是运行时调用:
Agent 启动
│
├── 加载 skills/api-conventions/SKILL.md 全文
├── 加载 skills/error-handling-patterns/SKILL.md 全文
│
├── 拼接到 Agent 的系统提示中:
│ [Agent 自身的 Markdown 正文]
│ [== api-conventions Skill 全文 ==]
│ [== error-handling-patterns Skill 全文 ==]
│
└── Agent 开始工作,自然地运用这些知识3.3 Agent 与 Hooks 的交互
Hooks 有两个层面:
层面一:Agent 自身定义的 Hooks(仅在 Agent 活跃时运行)
yaml
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-command.sh"
PostToolUse:
- matcher: "Edit|Write"
hooks:
- type: command
command: "./scripts/run-linter.sh"- 这些 Hook 仅在此 Agent 运行期间生效
- Agent 结束后自动清理,不影响主对话
- 支持所有 Hook 事件(PreToolUse、PostToolUse 等)
- Agent 中定义的
Stop事件会被自动转换为SubagentStop
层面二:主会话对 Agent 生命周期的 Hooks
在 settings.json 中可以监听 Agent 的启停:
json
{
"hooks": {
"SubagentStart": [{
"matcher": "browser-tester",
"hooks": [{ "type": "command", "command": "./scripts/start-browser.sh" }]
}],
"SubagentStop": [{
"matcher": "browser-tester",
"hooks": [{ "type": "command", "command": "./scripts/cleanup.sh" }]
}]
}
}两层 Hooks 的关系:
主会话 settings.json Hooks Agent 自身 Hooks
───────────────────── ──────────────────
SubagentStart("browser-tester") (Agent 启动)
→ start-browser.sh │
├── PreToolUse("Bash")
│ → validate-command.sh
│
├── [Agent 执行工具...]
│
├── PostToolUse("Edit")
│ → run-linter.sh
│
└── Stop (转为 SubagentStop)
SubagentStop("browser-tester")
→ cleanup.sh3.4 Agent 与 Memory 的交互
yaml
memory: project| 作用域 | 存储路径 | 适用场景 |
|---|---|---|
user | ~/.claude/agent-memory/<name>/ | 跨项目通用知识 |
project | .claude/agent-memory/<name>/ | 项目级知识(可提交 Git) |
local | .claude/agent-memory-local/<name>/ | 项目级私有(gitignore) |
启用 memory 后:
- Agent 的系统提示中会注入
MEMORY.md的前 200 行(或 25KB) - 自动启用 Read/Write/Edit 工具(即使
tools没有列出) - Agent 可以在执行过程中主动更新 MEMORY.md,积累跨会话知识
3.5 Agent 与 Worktree 隔离的交互
yaml
isolation: worktree- Agent 在独立的 git worktree 目录中运行
- 有自己的文件系统和分支,不影响主工作区
- Agent 结束后若无任何修改,worktree 自动清理
- 适合并行任务——多个 Agent 同时工作互不干扰
4. 插件 Agent 的安全限制
重要:插件(Plugin)中定义的 Agent 有安全限制——以下三个字段会被忽略:
| 被禁止的字段 | 原因 |
|---|---|
hooks | 防止插件通过 Hook 执行任意脚本 |
mcpServers | 防止插件连接未经审查的外部服务 |
permissionMode | 防止插件绕过权限控制 |
如果你需要这些能力,有两种方式:
- 复制到本地:把 Agent 文件从插件复制到
.claude/agents/或~/.claude/agents/,本地 Agent 没有这些限制 - 全局权限规则:在
settings.json的permissions.allow中添加规则(但会影响整个会话)
各来源 Agent 的优先级:
| 来源 | 优先级 | 限制 |
|---|---|---|
--agents CLI 参数 | 最高 | 无(仅当前会话) |
.claude/agents/(项目级) | 高 | 无 |
~/.claude/agents/(用户级) | 中 | 无 |
Plugin agents/ 目录 | 最低 | 禁用 hooks/mcpServers/permissionMode |
5. 完整交互流程图
以一个实际场景说明所有组件是如何协作的:
场景:用户说"测试登录页面的表单验证功能"
用户: "测试登录页面的表单验证功能"
│
│ ① Claude 匹配到 browser-tester 的 description
│
▼
Claude 决定委派给 browser-tester Agent
│
│ ② 触发 SubagentStart Hook → start-browser.sh
│ ③ 创建 worktree(isolation: worktree)
│ ④ 连接内联 MCP Server(playwright)
│ ⑤ 加载 Skills 全文注入上下文
│ ⑥ 加载 MEMORY.md 前 200 行注入上下文
│
▼
Agent 开始在独立上下文中工作
│
├── 调用 playwright.navigate("http://localhost:3000/login")
│ └── PreToolUse Hook → 无匹配(仅匹配 Bash)→ 放行
│
├── 调用 playwright.screenshot()
│ └── 返回页面截图
│
├── 调用 Bash("npm test -- --grep login")
│ └── PreToolUse Hook → matcher="Bash" → validate-command.sh → 放行
│ └── 返回测试结果
│
├── 调用 Edit("src/login.test.ts", ...)
│ └── PostToolUse Hook → matcher="Edit" → run-linter.sh → lint 修复
│
├── Agent 更新 MEMORY.md:记录登录页的表单结构和测试模式
│
└── Agent 生成结果摘要
│
▼
Agent 结束
│
├── ⑦ 触发 SubagentStop Hook → cleanup.sh
├── ⑧ 断开 playwright MCP Server
├── ⑨ 检查 worktree 是否有修改 → 有 → 保留
└── ⑩ 摘要结果返回主对话(不含中间过程的详细输出)6. 总结
Agent 与各组件的交互本质上是声明式组合——Agent 不需要写代码去"调用" MCP 或"触发" Hook,而是通过 YAML frontmatter 声明"我需要什么",Claude Code 运行时负责编排:
| 组件 | Agent 如何声明 | 运行时行为 |
|---|---|---|
| MCP | mcpServers 字段 | Agent 启动时连接,结束时断开;工具自动可用 |
| Skills | skills 字段 | 全文注入 Agent 的系统提示,成为领域知识 |
| Hooks | hooks 字段 | 仅在 Agent 活跃期间生效,结束后清理 |
| Memory | memory 字段 | MEMORY.md 注入上下文,Agent 可读写积累知识 |
| Worktree | isolation: worktree | Agent 在独立目录工作,不污染主工作区 |
| Model | model 字段 | Agent 可以用不同于主对话的模型(如 haiku 降低成本) |
| Tools | tools / disallowedTools | 控制 Agent 可用的内建工具子集 |
核心设计哲学:Agent 是一个声明式的能力组合体,Plugin 是它的分发载体。 Plugin 把 Agent 连同它依赖的 Skills、Hooks、MCP Server 一起打包分发,用户安装一个 Plugin 就获得了一整套协作能力。
Q5: Claude Code 哪些设计体现了 Harness(驾驭)的思想?
1. 什么是 Harness 思想
Harness 原意是马具/缰绳——不是把马关起来,而是在不削弱马力的前提下控制方向。
在 AI Agent 语境下,Harness 思想是指:
不限制 AI 的能力上限,但在每个关键节点设置可控的约束和检查点,确保 AI 的行为始终在人类可预期、可审计、可中断的范围内运行。
它与"限制"的区别在于:限制是做减法(禁止 AI 做某事),Harness 是加缰绳(让 AI 能做但可控地做)。Claude Code 的整个架构可以说是 Harness 思想的教科书级实现。
2. 架构层面:模型无手无脚,CLI 是它的身体
Claude Code 最根本的 Harness 设计是双系统架构:
┌──────────────────────┐ ┌──────────────────────┐
│ Claude 模型(大脑) │ │ Claude Code CLI(身体)│
│ │ 请求/响应 │ │
│ • 能推理 │ ◄──────────► │ • 能执行 │
│ • 能决策 │ │ • 有权限系统 │
│ • 能生成工具调用 │ │ • 有沙箱隔离 │
│ • 不能直接执行任何事 │ │ • 有 Hook 守卫 │
└──────────────────────┘ └──────────────────────┘模型永远不直接执行任何操作。它只能返回一个结构化的"工具调用请求":
json
{ "type": "tool_use", "name": "Bash", "input": { "command": "rm -rf /" } }这个请求必须经过 CLI 侧的完整管控链路才可能被执行。模型对真实世界零直接访问权——它的所有能力都经由 CLI 这个"驾驭层"中介。
这是最底层的 Harness:能力与执行权的分离。
3. 权限层面:分层递进的缰绳
3.1 Deny > Ask > Allow 三级权限
用户请求 "删除这个文件"
│
▼
Claude 模型返回: tool_use(Bash, "rm important.txt")
│
▼
┌─────────────────────┐
│ ① Deny 规则检查 │ ← Bash(rm -rf *) 在黑名单?→ 直接阻止
└─────────┬───────────┘
│ 未命中
┌─────────▼───────────┐
│ ② Ask 规则检查 │ ← 需要用户确认?→ 弹出审批提示
└─────────┬───────────┘
│ 用户批准
┌─────────▼───────────┐
│ ③ Allow 规则检查 │ ← 在白名单中?→ 自动放行
└─────────┬───────────┘
│
▼
执行操作Deny 规则不可覆盖——即使用户在 Allow 中配置了同样的规则,Deny 仍然优先。这是一个硬性缰绳,防止任何绕过。
3.2 六种权限模式——渐进式放权
| 模式 | 缰绳松紧 | 类比 |
|---|---|---|
Plan | 最紧 | 只许看,不许动——马只能走路线 |
Default | 紧 | 每步都问——骑手时刻拉缰 |
Accept Edits | 中等 | 信任文件编辑,Shell 命令仍审批——松一半缰绳 |
Auto | 较松 | 半自主执行,后台安全分类器检查——缰绳换成了围栏 |
Don't Ask | 反向 | 未预批准的操作直接拒绝——CI/CD 场景的硬边界 |
Bypass | 最松 | 跳过所有提示,但仍保护 .git/.claude——卸掉缰绳但保留笼头 |
核心思想:不是二元的"全开/全关",而是提供一个连续的信任光谱,用户根据场景选择合适的松紧度。
4. 执行层面:每一步都有检查点
4.1 Hook 系统——确定性守卫
CLAUDE.md 中的指令遵循率约 80%(模型可能忽略)。Hooks 的执行率是 100%——这就是 Harness 思想的精髓:关键约束不能依赖模型的"自觉",必须由确定性机制保证。
工具调用请求
│
├── PreToolUse Hook ──── 100% 执行
│ ├── command: 脚本校验 → exit 2 可阻止
│ ├── prompt: LLM 评估 → 是/否判断
│ └── agent: 子智能体验证 → 复杂审查
│
├── [执行工具操作]
│
└── PostToolUse Hook ──── 100% 执行
└── command: 自动 lint/format/安全扫描对比:
| 机制 | 执行率 | 本质 |
|---|---|---|
| CLAUDE.md 指令 | ~80% | 建议(Advisory)—— "请不要做 X" |
| Hook | 100% | 强制(Enforcement)—— "不允许做 X" |
这正是 Harness 与"口头约定"的区别:缰绳在手里,不是挂在马脖子上的牌子。
4.2 沙箱隔离——操作系统级围栏
┌────────────────────────────────────────────┐
│ 操作系统沙箱 │
│ macOS: Seatbelt / Linux: bubblewrap │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 文件系统 │ │
│ │ 项目目录 → 可读写 │ │
│ │ 父目录 → 只读 │ │
│ │ 系统目录 → 禁止 │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 网络 │ │
│ │ 已批准域名 → 允许 │ │
│ │ 其他域名 → 代理拦截,阻止 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 即使模型+CLI+Hooks 全部"同意"执行, │
│ 沙箱仍然是最后一道物理围栏。 │
└────────────────────────────────────────────┘这是纵深防御的 Harness 设计——不依赖任何单一机制,而是层层嵌套:
权限系统(Deny) → Hook(PreToolUse) → CLI 执行检查 → OS 沙箱
↑ ↑ ↑ ↑
第一道缰绳 第二道缰绳 第三道缰绳 围栏(最后防线)5. 资源层面:防止失控
5.1 上下文压缩——防止记忆膨胀
Agent 的上下文窗口是有限的(200K tokens)。无限制地累积工具输出会导致上下文溢出、任务失忆。压缩系统就是"缰绳":
| 机制 | 触发 | 作用 |
|---|---|---|
| 微压缩 | 单次工具输出过大 | 把大型结果卸载到磁盘,上下文只留引用 |
| 自动压缩 | 上下文达 95% | 自动总结历史,释放空间 |
| 手动压缩 | /compact | 用户主动控制保留什么 |
5.2 maxTurns / maxBudget——防止无限循环和费用失控
bash
claude -p --max-turns 5 --max-budget-usd 2.00 "重构 auth 模块"| 约束 | 作用 |
|---|---|
--max-turns | 限制 Agent 循环的最大轮次,防止死循环 |
--max-budget-usd | 限制单次任务的最大花费,防止费用失控 |
maxTurns(Agent frontmatter) | 限制子智能体的最大轮次 |
这是资源维度的缰绳——即使模型的推理完全正确,也要在预算和时间上设限。
5.3 子智能体深度限制
主对话 → 可以创建子智能体
│
└── 子智能体 → 不可以再创建子智能体(深度 = 1)防止 Agent 自我复制导致递归爆炸。这是拓扑层面的缰绳。
6. 工具层面:最小权限原则
6.1 只读工具不需权限,写入工具必须审批
| 操作类型 | 代表工具 | 权限 |
|---|---|---|
| 只读(无副作用) | Read, Glob, Grep, TodoWrite | 自动放行 |
| 有副作用 | Write, Edit, Bash, WebFetch | 需要审批 |
6.2 Agent 的工具约束
yaml
# 白名单模式:只允许指定工具
tools: Read, Grep, Glob
# 黑名单模式:禁止指定工具
disallowedTools: Write, Edit
# 限制可生成的子智能体类型
tools: Agent(worker, researcher), Read, Bash6.3 命令黑名单
CLI 内建了危险命令黑名单(curl、wget 等),即使 Allow 规则没有显式排除,也默认阻止。
7. 可观测层面:一切可审计
Harness 不仅是"控制",还包括"可见"——你必须看得到马往哪跑。
| 可观测机制 | 作用 |
|---|---|
/cost | 实时查看 token 用量和费用 |
/context | 可视化上下文窗口使用状况 |
/diff | 查看 Claude 做了哪些文件修改 |
events.jsonl | Worktree 生命周期事件的 append-only 日志 |
| Hook 事件流 | 25+ 个生命周期事件可被监听 |
Ctrl+O | 切换详细输出,查看完整工具调用过程 |
Escape × 2 | 随时回退/撤销 Claude 的操作 |
| Agent Memory | 子智能体的 MEMORY.md 可被人类审查 |
| 子智能体 transcript | ~/.claude/projects/{project}/{session}/subagents/ 下保存完整对话记录 |
8. 插件层面:第三方代码的信任边界
插件是外部来源的代码,Harness 思想在这里体现为默认不信任:
| 约束 | 说明 |
|---|---|
插件 Agent 禁用 hooks | 防止插件通过 Hook 执行任意脚本 |
插件 Agent 禁用 mcpServers | 防止插件连接未审查的外部服务 |
插件 Agent 禁用 permissionMode | 防止插件绕过权限控制 |
| 插件缓存隔离 | 安装的插件被复制到 ~/.claude/plugins/cache/,不直接引用原路径 |
| 路径不能外逸 | 插件不能引用自身目录之外的文件(../ 无效) |
| 命名空间隔离 | 插件 Skill 命名为 /plugin-name:skill,防止命名冲突 |
9. 总结:Harness 设计的六层纵深
┌─────────────────────────────────────────────────────┐
│ 第一层:架构分离 │
│ 模型无执行权,所有操作经由 CLI 中介 │
├─────────────────────────────────────────────────────┤
│ 第二层:权限系统 │
│ Deny > Ask > Allow,六种权限模式渐进放权 │
├─────────────────────────────────────────────────────┤
│ 第三层:Hook 守卫 │
│ 100% 确定性执行,PreToolUse 可阻止/修改操作 │
├─────────────────────────────────────────────────────┤
│ 第四层:资源约束 │
│ maxTurns、maxBudget、子智能体深度=1、上下文压缩 │
├─────────────────────────────────────────────────────┤
│ 第五层:沙箱隔离 │
│ OS 级文件系统/网络隔离 + Worktree 工作区隔离 │
├─────────────────────────────────────────────────────┤
│ 第六层:可观测性 │
│ 费用/上下文/diff/事件流/transcript 全方位可审计 │
└─────────────────────────────────────────────────────┘Claude Code 的设计哲学可以用一句话概括:
给 AI 尽可能强大的能力,但在每一层都系上缰绳——不是为了限制它跑多快,而是为了确保它跑在正确的方向上,并且骑手随时可以勒停。
这就是 Harness 思想的完整体现:能力最大化、风险可控化、行为可审计化。
Q6: Claude Code 的 diff 能力是如何实现的?
Claude Code 的"diff 能力"涉及三个层面:Edit 工具如何精准修改文件、修改结果如何生成 diff 展示、/diff 命令如何提供交互式 diff 查看器。下面从底层到上层逐一拆解。
1. Edit 工具:精确字符串替换(而非传统 diff/patch)
Claude Code 的文件修改不使用传统的 diff + patch 流程,而是采用**精确字符串替换(Exact String Replacement)**方式。
1.1 工具参数
json
{
"type": "tool_use",
"name": "Edit",
"input": {
"file_path": "src/auth.ts",
"old_string": "function login(user) {",
"new_string": "function login(user: User): Promise<Session> {",
"replace_all": false
}
}模型需要精确提供"要被替换的原文"和"替换后的新文"——不是行号,不是正则,而是文本本身。
1.2 Edit 算法流程
输入: old_string, new_string, file_path
│
▼
┌──────────────────────────────┐
│ Step 1: 精确匹配 │
│ 在文件中搜索 old_string 的 │
│ 字面量完全匹配 │
└─────────────┬────────────────┘
│
找到?────┴────没找到?
│ │
▼ ▼
┌──────────┐ ┌─────────────────────┐
│ 检查唯一性│ │ Step 2: 模糊匹配 │
│ │ │ • 尾部空格标准化 │
│ 唯一 → 替换│ │ • 缩进标准化 │
│ 多处 → 报错│ │ • CRLF/LF 标准化 │
└──────────┘ │ • 上下文行扩展匹配 │
└──────────┬──────────┘
│
找到?────┴────仍然没找到?
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ 替换+警告 │ │ 返回错误 │
└──────────┘ │ 要求模型重新 │
│ 读取文件后重试 │
└──────────────┘1.3 替换前的校验
| 校验项 | 目的 |
|---|---|
| 文件存在 | 防止通过 Edit 创建新文件(应该用 Write) |
| old_string 找到 | 确保编辑目标正确 |
| 唯一匹配 | old_string 必须在文件中唯一(否则用 replace_all) |
| 内容有变化 | 防止 old_string == new_string 的空操作 |
1.4 失败时的自愈循环
| 错误 | 原因 | 模型的自动应对 |
|---|---|---|
| "old_string not found" | 文件在上次 Read 后被修改了 | 重新 Read 文件,用最新内容重试 |
| "Multiple matches" | old_string 不够唯一 | 增加上下文行数使其唯一 |
| "File not found" | 路径错误 | 用 Glob 搜索正确路径 |
这是 Agent Loop 的核心优势——失败不是终点,而是下一轮循环的输入。模型根据错误信息调整策略,自动重试。
1.5 为什么不用行号?
传统 diff/patch 使用行号定位,但在 AI Agent 场景下行号有致命缺陷:
| 方式 | 问题 |
|---|---|
| 行号定位 | 前面的编辑会导致后续行号偏移,多步编辑时极易出错 |
| 正则替换 | 过于灵活,容易误匹配,LLM 生成正则不够可靠 |
| 精确字符串 | 不依赖位置,不依赖格式——只要文本在文件中存在且唯一即可 |
精确字符串替换是对 LLM 最友好的编辑原语——模型只需要"看到什么就写什么",无需计算行号偏移。
2. Diff 生成:从替换操作到 Unified Diff
Edit 工具执行替换后,需要生成人类可读的 diff 展示。这涉及两个关键技术。
2.1 Marker-Based 位置追踪
多次替换时,前面的替换会改变后面的行号位置。Claude Code 用**标记法(Marker)**解决这个问题:
原始文件: "line 1\nfoo bar\nline 3\nfoo baz"
第一步:替换 "foo" → "[MARKER_1]replaced"
→ "line 1\n[MARKER_1]replaced bar\nline 3\n[MARKER_1]replaced baz"
第二步:替换 "line 3" → "[MARKER_2]LINE THREE"
→ "line 1\n[MARKER_1]replaced bar\n[MARKER_2]LINE THREE\n..."
定位阶段:找到每个 MARKER 的位置,计算其前面有多少个换行符
→ MARKER_1 在第 2 行,MARKER_2 在第 3 行
清理阶段:移除所有 MARKER,返回干净的结果文件
→ "line 1\nreplaced bar\nLINE THREE\nreplaced baz"
→ 修改行号: [2, 3]Marker 格式:__REPLACE_MARKER_<9位随机串>_<计数器>__,确保不会与文件内容冲突。
2.2 Unified Diff 生成
替换完成后,用 diff 库对比替换前后的文件内容,生成标准 Unified Diff 格式:
diff
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -1,3 +1,3 @@
import { db } from './database';
-function login(user) {
+function login(user: User): Promise<Session> {
const session = await db.createSession(user);这就是用户在终端中看到的 diff 输出。
2.3 IDE 集成时的 ACP Diff 格式
当 Claude Code 通过 ACP(Agent Client Protocol)与 IDE(如 Cursor、Zed)集成时,diff 被转换为结构化的 ACP 格式:
json
{
"type": "diff",
"filePath": "src/auth.ts",
"oldText": "function login(user) {",
"newText": "function login(user: User): Promise<Session> {",
"startLine": 3
}IDE 客户端收到这个结构后,可以渲染为:
- 侧边栏的高亮 diff 视图
- 内联的添加/删除标记
- 可点击的文件导航链接
2.4 Hunk 解析算法
从 Unified Diff 解析出 ACP 格式时,需要对每个 hunk 进行行分类:
Unified Diff 行前缀:
"-" → 旧内容中被删除的行 → 放入 oldText
"+" → 新内容中被添加的行 → 放入 newText
" " → 上下文行(未变化) → 同时放入 oldText 和 newText3. /diff 交互式查看器
/diff 是 Claude Code 内建的交互式 diff 查看器,让用户在提交前审查所有修改。
3.1 功能
输入 /diff 后:
┌─────────────────────────────────────────────────┐
│ /diff - Interactive Diff Viewer │
│ │
│ [← →] 切换视图: │
│ • Git Diff(当前所有未提交的修改) │
│ • Turn 1 Diff(第一轮 Claude 做的修改) │
│ • Turn 2 Diff(第二轮 Claude 做的修改) │
│ • Turn N Diff(第 N 轮...) │
│ │
│ [↑ ↓] 浏览不同文件 │
│ │
│ 显示格式: 标准 Unified Diff │
│ - 红色: 删除的行 │
│ + 绿色: 新增的行 │
│ 白色: 上下文行 │
└─────────────────────────────────────────────────┘3.2 两种 diff 视图
| 视图 | 数据来源 | 用途 |
|---|---|---|
| Git Diff | git diff(未提交的修改总和) | 查看 Claude 和你总共做了什么修改 |
| Per-Turn Diff | 每轮 Agent Loop 中 Edit/Write 操作的记录 | 查看 Claude 每一步具体改了什么 |
Per-Turn Diff 是 /diff 最有价值的能力——大规模重构时,你可以逐轮审查 Claude 的修改,而不是面对一个巨大的 git diff 无从下手。
3.3 实现原理
Claude Code 内部维护每轮操作的 diff 记录:
Turn 1: Edit("src/auth.ts", old1, new1) → diff_1
Turn 2: Edit("src/auth.ts", old2, new2) → diff_2
Turn 3: Write("src/new-file.ts", content) → diff_3
...
/diff 命令:
├── 读取 git diff HEAD(总 diff)
├── 读取每轮的 diff 记录
└── 提供交互式 UI 切换浏览4. Write 工具的 diff 处理
与 Edit(局部替换)不同,Write 是整个文件覆写。diff 处理方式也不同:
| 场景 | diff 行为 |
|---|---|
| 创建新文件 | diff 展示所有行为新增(全绿) |
| 覆写已有文件 | diff 对比旧文件 vs 新内容(标准 diff) |
在 ACP 格式中,Write 操作用 oldText: null 表示"这是整个文件的写入,不是局部编辑"。
5. 跨平台兼容
diff 系统需要处理不同操作系统的行尾差异:
| 系统 | 行尾 | 处理 |
|---|---|---|
| Unix/macOS | \n | 标准 |
| Windows | \r\n | 标准化为 \n 后匹配 |
| 经典 Mac | \r | 标准化为 \n 后匹配 |
| 混合 | 混合 | 用 `/\r\n |
已知问题:Windows/SSH 场景下 CRLF 行尾可能导致多行编辑的 diff 预览匹配失败(old_string 中的 \n 与文件中的 \r\n 不匹配)。
6. 总结:三层 diff 能力
┌─────────────────────────────────────────────────────┐
│ 第一层:Edit 工具 —— 精确字符串替换 │
│ • 不用行号、不用正则,用文本本身定位 │
│ • 精确匹配 → 模糊匹配 → 报错自愈 三级策略 │
│ • Marker-Based 位置追踪解决多步编辑行号偏移 │
├─────────────────────────────────────────────────────┤
│ 第二层:Diff 生成 —— 从替换结果到可读 diff │
│ • 替换前后文件对比 → Unified Diff 格式 │
│ • Hunk 解析 → ACP 结构化格式(供 IDE 渲染) │
│ • 每轮操作独立记录 diff(Per-Turn Diff) │
├─────────────────────────────────────────────────────┤
│ 第三层:/diff 查看器 —— 交互式审查 │
│ • Git Diff 视图(总修改)+ Per-Turn 视图(逐轮修改) │
│ • 方向键切换文件和轮次 │
│ • 提交前审查,防止大规模重构中的意外修改 │
└─────────────────────────────────────────────────────┘核心设计选择:用"精确字符串替换"代替"行号+diff/patch",是 Claude Code 对 LLM 能力特点的精准适配——LLM 擅长理解和生成文本内容,但不擅长维护精确的数值位置(行号)。这个选择让 Edit 工具的成功率大幅高于基于行号的方案,也使得错误自愈变得简单(重新读文件即可,不需要重新计算偏移量)。
附:Unified Diff 与 ACP Diff 格式详解
一、Unified Diff 格式
Unified Diff 是 git diff、diff -u 和大多数代码审查工具使用的标准 diff 格式。Claude Code 的 Edit/Write 工具在内部生成的 diff 也采用这个格式。
1. 完整结构
diff
diff --git a/src/auth.ts b/src/auth.ts
index 3a4b5c6..7d8e9f0 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -10,7 +10,9 @@ import { db } from './database';
export class AuthService {
private tokenSecret: string;
- private sessionTimeout = 3600;
+ private sessionTimeout: number;
+ private maxRetries = 3;
- constructor(secret: string) {
+ constructor(secret: string, timeout = 3600) {
this.tokenSecret = secret;
+ this.sessionTimeout = timeout;
}2. 逐行解读
文件头(Header):
--- a/src/auth.ts ← 原文件(修改前)
+++ b/src/auth.ts ← 新文件(修改后)---标记原文件路径(a/ 前缀是 git 约定,表示 "before")+++标记新文件路径(b/ 前缀表示 "after")- 实际的
diff -u命令还会在路径后附带时间戳
Hunk 头(Hunk Header):
@@ -10,7 +10,9 @@ import { db } from './database';解读:
| 部分 | 含义 |
|---|---|
@@ | Hunk 的开始标记 |
-10,7 | 原文件从第 10 行开始,展示 7 行 |
+10,9 | 新文件从第 10 行开始,展示 9 行(因为新增了 2 行) |
@@ | Hunk 头结束标记 |
import { db }... | 最近的函数/类签名(可选,由 git 自动添加,便于定位上下文) |
行内容标记:
| 前缀 | 含义 | 颜色(终端) |
|---|---|---|
(空格) | 上下文行——两个文件中都有的未修改行 | 白色/灰色 |
- | 删除行——只存在于原文件中 | 红色 |
+ | 新增行——只存在于新文件中 | 绿色 |
"修改"的表示:没有专门的"修改"标记。一行被修改表示为先删后增:
diff
- private sessionTimeout = 3600; ← 删除旧行
+ private sessionTimeout: number; ← 新增新行3. 单行号 vs start,count 格式
Hunk 头中的行号有两种写法:
| 写法 | 含义 | 示例 |
|---|---|---|
-10,7 | 从第 10 行开始,共 7 行 | 标准多行 hunk |
-10 | 等价于 -10,1,只有 1 行 | 单行变更 |
-0,0 | 空文件(新增文件时原文件为空) | +++ b/newfile.ts |
4. 上下文行的作用
默认展示变更行上下各 3 行的上下文。上下文行的目的:
- 定位:帮助读者理解变更发生在代码的什么位置
- 应用 patch:
git apply靠上下文行来定位 patch 的应用位置(即使行号因其他修改而偏移) - 冲突检测:上下文不匹配说明文件已被修改,patch 可能不适用
5. 多个 Hunk
一个文件中如果有多处不连续的修改,会产生多个 Hunk,每个都有独立的 @@ ... @@ 头:
diff
--- a/config.ts
+++ b/config.ts
@@ -5,3 +5,4 @@
const PORT = 3000;
+const HOST = '0.0.0.0';
const DEBUG = false;
@@ -20,4 +21,4 @@
export function getConfig() {
- return { port: PORT, debug: DEBUG };
+ return { port: PORT, host: HOST, debug: DEBUG };
}二、ACP Diff 格式
ACP(Agent Client Protocol)是 Claude Code 与 IDE(如 Cursor、Zed)集成时使用的通信协议。Unified Diff 是文本格式,适合终端显示;ACP Diff 是结构化 JSON 格式,适合 IDE 渲染。
1. ACP Diff 结构
json
{
"type": "diff",
"filePath": "src/auth.ts",
"oldText": " private sessionTimeout = 3600;\n\n constructor(secret: string) {\n this.tokenSecret = secret;\n }",
"newText": " private sessionTimeout: number;\n private maxRetries = 3;\n\n constructor(secret: string, timeout = 3600) {\n this.tokenSecret = secret;\n this.sessionTimeout = timeout;\n }",
"startLine": 12
}| 字段 | 类型 | 含义 |
|---|---|---|
type | "diff" | 标识这是一个 diff 类型的内容块 |
filePath | string | 被修改的文件路径 |
oldText | string | null | 被替换的原文本(null 表示整个文件写入,非局部编辑) |
newText | string | 替换后的新文本 |
startLine | number | 变更起始的行号 |
2. 从 Unified Diff 到 ACP Diff 的转换
Claude Code 的 Edit 工具执行后,会生成 Unified Diff。当通过 ACP 协议发送给 IDE 时,需要做 Hunk 解析转换:
Unified Diff (文本) ACP Diff (结构化)
───────────────── ──────────────────
@@ -12,5 +12,7 @@ {
- timeout = 3600; "filePath": "src/auth.ts",
+ timeout: number; "oldText": " timeout = 3600;...",
+ maxRetries = 3; "newText": " timeout: number;...",
"startLine": 12
───────→ }Hunk 解析算法:
遍历 Hunk 中的每一行,根据前缀分流:
对于 Hunk 中的每一行:
前缀 = 行的第一个字符
内容 = 行去掉前缀后的文本
if 前缀 == "-":
追加到 oldText
elif 前缀 == "+":
追加到 newText
elif 前缀 == " " (空格):
同时追加到 oldText 和 newText ← 上下文行两边都要3. Edit vs Write 的 ACP Diff 区别
| 操作 | oldText | newText | 含义 |
|---|---|---|---|
| Edit(局部替换) | 被替换的原文片段 | 替换后的新文片段 | IDE 展示局部 diff |
| Write(整个文件创建) | null | 整个文件内容 | IDE 展示为新增文件 |
| Write(整个文件覆写) | 原文件完整内容 | 新文件完整内容 | IDE 展示全文件 diff |
oldText: null 是一个特殊信号——告诉 IDE 客户端"这不是局部编辑,而是整个文件的写入",IDE 可以据此选择不同的展示方式(如用"新增文件"视图而非 diff 视图)。
4. IDE 客户端如何渲染 ACP Diff
IDE 收到 ACP Diff 后,可以渲染为多种视图:
ACP Diff JSON
│
├── 侧边栏 Side-by-Side 视图
│ 左侧: oldText(红色背景高亮删除部分)
│ 右侧: newText(绿色背景高亮新增部分)
│
├── 内联 Inline 视图
│ 同一列中交替显示删除行和新增行
│
├── 审批弹窗
│ 显示 diff + [Accept] [Reject] 按钮
│
└── 文件导航
点击 filePath 跳转到对应文件的 startLine 位置三、两种格式对比
| 维度 | Unified Diff | ACP Diff |
|---|---|---|
| 格式 | 纯文本 | 结构化 JSON |
| 使用场景 | 终端、git、代码审查 | IDE 集成、Agent-IDE 通信 |
| 信息粒度 | 行级(每行标记 +/-/空格) | 块级(oldText/newText 整块) |
| 位置信息 | Hunk 头 @@ -start,count +start,count @@ | startLine 字段 |
| 多处修改 | 多个 Hunk 拼接 | 多个独立的 diff 对象 |
| 是否标准 | 是(POSIX/GNU 标准) | 否(ACP 协议专用) |
| 生成方 | diff 库 / git diff | Claude Code CLI 从 Unified Diff 转换 |
| 消费方 | 人类阅读 / git apply | IDE 客户端渲染 |
四、在 Claude Code 数据流中的位置
Edit 工具执行替换
│
├── 替换前文件内容 + 替换后文件内容
│
▼
diff 库对比 → 生成 Unified Diff
│
├── 终端模式 → 直接渲染(红/绿高亮)→ 用户在终端中看到
│
└── IDE 模式 → Hunk 解析 → 转为 ACP Diff JSON
│
└── 通过 ACP 协议发送给 IDE
│
└── IDE 渲染为侧边栏/内联/审批视图Q7: Claude Code 在检索方面的原理和逻辑(索引?grep?正则?)
1. 核心理念:"Search, Don't Index"
Claude Code 的检索哲学可以用一句话概括:
不建索引,不用 RAG,不用 Embedding——全靠 grep 暴力搜索 + 模型自主决策搜索策略。
这个设计选择有明确的历史背景:Claude Code 早期版本曾尝试用 Voyage Embeddings 做 RAG 式的语义代码搜索,但 Anthropic 内部基准测试显示,基于 ripgrep 的 Agent 式搜索性能更优,且运维复杂度更低——不需要索引同步,不需要外部 Embedding 服务,没有安全隐患。
来源:Latent Space Podcast - Claude Code(2025年5月)
2. 两个内建检索工具
Claude Code 只有两个内建检索工具,都极其简单:
2.1 Grep —— 内容搜索(基于 ripgrep)
参数:
- pattern (必选): 正则表达式模式
- path (可选): 搜索路径(默认项目根目录)
- glob (可选): 文件过滤模式(如 "*.ts")
- type (可选): 文件类型(如 "js", "py")
- output_mode (可选): content / files_with_matches / count
- multiline (可选): 是否跨行匹配
- -A/-B/-C (可选): 上下文行数
底层: ripgrep (rg)核心特性:
| 特性 | 说明 |
|---|---|
| 基于 ripgrep | Rust 实现,极快,支持并行搜索 |
| 正则表达式 | 完整正则语法(非简单字符串匹配) |
| 结果上限 | 输出上限为数千行,防止撑爆上下文 |
| 不需要索引 | 每次搜索都是实时遍历文件系统 |
| 自动忽略 | 遵循 .gitignore 规则,自动跳过 node_modules 等 |
典型调用:
json
{
"name": "Grep",
"input": {
"pattern": "function\\s+authenticate",
"glob": "*.ts",
"output_mode": "content",
"-C": 3
}
}2.2 Glob —— 文件名搜索(基于文件系统)
参数:
- glob_pattern (必选): 匹配模式(如 "**/*.test.ts")
- target_directory (可选): 搜索目录
底层: 文件系统 glob 匹配核心特性:
| 特性 | 说明 |
|---|---|
| 路径模式匹配 | **/*.ts、src/**/test_*.py 等标准 glob 语法 |
| 按修改时间排序 | 最近修改的文件排在前面(更可能是相关文件) |
| 自动递归 | 非 **/ 开头的模式自动添加 **/ 前缀 |
| 不搜内容 | 只匹配文件名/路径,不读取文件内容 |
3. 没有索引,搜索是怎么工作的?
这是最关键的问题。传统 IDE(如 VS Code)和 RAG 系统都依赖预建索引来加速搜索。Claude Code 完全不建索引——那它如何在大型代码库中高效检索?
答案是:靠模型的推理能力驱动多轮渐进式搜索。
3.1 Agent 式搜索流程
用户: "找到用户认证的实现代码"
│
│ Claude 不知道代码在哪,开始推理搜索策略
│
▼
Turn 1: Glob("**/*auth*")
→ 发现: src/auth/login.ts, src/auth/middleware.ts, tests/auth.test.ts
│
│ Claude 分析文件名,决定先看哪个
│
▼
Turn 2: Read("src/auth/login.ts")
→ 看到 import { validateToken } from '../utils/token'
│
│ Claude 发现还有相关文件需要看
│
▼
Turn 3: Grep("validateToken", glob="*.ts")
→ 找到 src/utils/token.ts 中的实现
│
│ Claude 找到了完整的认证链路
│
▼
Turn 4: Read("src/utils/token.ts")
→ 理解了完整实现关键洞察:搜索不是一次性操作,而是多轮推理驱动的渐进式探索。模型根据每一步的结果动态调整搜索策略——这就是"Agentic Search"的含义。
3.2 对比:索引搜索 vs Agent 搜索
| 维度 | 传统索引搜索(RAG/Embedding) | Claude Code Agent 搜索 |
|---|---|---|
| 前置准备 | 需要建索引(耗时、需同步) | 零准备,开箱即用 |
| 存储开销 | Embedding 索引占用磁盘/内存 | 无额外存储 |
| 同步问题 | 文件修改后索引可能过期 | 每次搜索都是实时结果 |
| 安全性 | Embedding 可能泄露代码语义 | 不外传任何数据 |
| 语义理解 | 依赖 Embedding 质量 | 依赖 LLM 推理能力 |
| 搜索成本 | 低(已索引) | 高(每次都遍历 + 多轮 LLM 调用) |
| 搜索精度 | Embedding 有"语义漂移"风险 | 精确匹配 + 模型判断 |
权衡:Claude Code 用更多的 token 和延迟换取了简洁性、实时性和安全性。
4. 模型如何决定搜索策略?
没有硬编码的搜索路由——模型自己判断该用什么工具、什么参数。这个能力来自训练数据和系统提示中的指引。
4.1 系统提示中的搜索指引
Claude Code 的系统提示包含类似以下的指引(基于行为推断):
- 优先使用 Grep 做内容搜索(基于 ripgrep,速度最快)
- 使用 Glob 查找文件(按文件名/路径模式匹配)
- 不要用 Bash 的 find/grep 命令——用内建的 Grep/Glob 工具
- 搜索前先想想:文件可能叫什么名字?代码中可能用什么变量名?
- 搜索结果太多时,缩小范围(加 glob 过滤、限制路径)
- 搜索结果太少时,放宽条件(去掉过滤、换关键词)4.2 模型的搜索决策树
任务: 找到某段代码
│
├── 知道文件名?─── 是 ──→ Read(文件路径)
│
├── 知道文件名模式?── 是 ──→ Glob("**/pattern*")
│ → 从结果中选择 → Read
│
├── 知道代码内容关键词?── 是 ──→ Grep("关键词")
│ → 定位文件和行号 → Read
│
├── 只有模糊描述?── 是 ──→ Glob 找可能的文件
│ → Read 几个最可能的
│ → 根据 import/引用线索 Grep 跟踪
│
└── 完全不知道?── 是 ──→ Glob("**/*") 看项目结构
→ 逐目录探索
→ 或委派给 Explore 子智能体4.3 Explore 子智能体——专用搜索 Agent
对于大规模代码探索,Claude 会委派给 Explore 子智能体:
| 特性 | 说明 |
|---|---|
| 模型 | Haiku(快速、低成本) |
| 工具 | 只读(Read, Glob, Grep),不可修改文件 |
| 独立上下文 | 搜索结果不占用主对话的上下文空间 |
| 返回摘要 | 只把关键发现返回主对话 |
三个彻底程度:
| 级别 | 行为 |
|---|---|
quick | 定向搜索,已知位置的快速查找 |
medium | 跨多个目录搜索 |
very thorough | 全面分析,跨多个位置和命名约定 |
5. MCP Tool Search:MCP 工具的"懒加载"检索
这不是代码检索,而是工具定义的检索——但它是 Claude Code 检索能力的重要组成部分。
问题:安装大量 MCP Server 后,所有工具的 JSON Schema 都被加载到上下文中,可能消耗 50K+ token(有开发者报告 66,000+ token),还没开始干活上下文就满了。
解决方案:MCP Tool Search(懒加载)
无 Tool Search(急加载):
启动时加载所有 100+ 工具定义 → 上下文占用 ~55K tokens
有 Tool Search(懒加载):
启动时只加载 1 个 search 工具 → 占用 ~500 tokens
模型需要某能力时 → search 工具用 BM25/正则 找到匹配工具
按需加载匹配到的工具定义 → 每个 ~600 tokens效果(Anthropic 官方基准):
- 上下文占用减少最高 95%
- 工具调用准确率提升 15%(噪声更少,模型更容易选对工具)
- 配合 Token 缓存,成本降低 最高 90%
配置:
bash
ENABLE_TOOL_SEARCH=auto # 默认(10% 上下文阈值时启用)
ENABLE_TOOL_SEARCH=auto:5 # 激进(5% 阈值)
ENABLE_TOOL_SEARCH=auto:20 # 保守(20% 阈值)
ENABLE_TOOL_SEARCH=true # 始终启用
ENABLE_TOOL_SEARCH=false # 禁用(全量加载)6. 社区扩展:当 Grep 不够用时
Claude Code 内建的 Grep + Glob 覆盖了约 90% 的搜索需求。剩下 10% 的场景可以通过社区工具补充:
| 工具 | 类型 | 能力 | 速度 | 适用场景 |
|---|---|---|---|---|
| Grep(内建) | ripgrep | 精确文本/正则匹配 | ~20ms | 90% 的日常搜索 |
| Glob(内建) | 文件系统 | 文件名模式匹配 | 极快 | 找文件 |
| ast-grep | Plugin | AST 结构化代码搜索 | ~200ms | 大规模重构/迁移 |
| Serena | MCP | 符号级导航(函数/类定义、引用) | ~100ms | 重构、符号跳转 |
| grepai | MCP | 语义搜索 + 调用链分析 | ~500ms | 不知道确切文本时的模糊搜索 |
决策原则:先用 Grep(最快),不够再升级到专用工具。
搜索决策流:
"我知道确切的文本" ────→ Grep(正则匹配)
│
├── 找到 → 完成
│
└── 没找到
│
"我知道函数/类名" ─────→ Serena(符号导航)
│
"我知道代码结构模式" ──→ ast-grep(AST 匹配)
│
"我只有模糊描述" ─────→ grepai(语义搜索)7. 为什么这种"笨"方法反而更好?
从工程角度看,Claude Code 的"每次都暴力搜索"方法看起来很"笨"——但实际效果超过了精心设计的索引系统。原因有三:
1. 实时性
索引有同步延迟——文件修改后索引可能过期。Agent 在编辑过程中频繁搜索(编辑→搜索→编辑→搜索),如果搜索结果是过期的索引,会导致连锁错误。ripgrep 每次都读实际文件,永远不会过期。
2. ripgrep 本身就够快
ripgrep 用 Rust 写的,利用 SIMD 指令和内存映射,在中大型代码库(10 万行级别)上单次搜索通常在 20-50ms 内完成。即使多轮搜索,总延迟也远小于一次 LLM API 调用的延迟。搜索本身不是瓶颈。
3. 模型推理弥补了"无索引"的劣势
传统搜索的索引解决的是"不知道在哪"的问题。但 LLM 可以通过推理来缩小搜索范围——它会根据项目结构、命名约定、import 路径来猜测代码位置,然后用 Grep 验证。这相当于用 LLM 的推理能力替代了索引的定位能力。
8. 总结
Claude Code 检索架构:
┌────────────────────────────────────────────────────────┐
│ 不建索引、不用 RAG、不用 Embedding │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Layer 1: 内建工具 │ │
│ │ Grep(ripgrep)—— 内容搜索,正则匹配 │ │
│ │ Glob —— 文件名搜索,路径模式匹配 │ │
│ └─────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 模型自主决策何时调用、用什么参数 │
│ │ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Layer 2: Agent 推理 │ │
│ │ 多轮渐进式搜索:Glob→Read→Grep→Read→... │ │
│ │ 根据每步结果动态调整策略 │ │
│ │ 失败自愈:换关键词、缩放范围、换工具 │ │
│ └─────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 大规模探索时委派 │
│ │ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Layer 3: Explore 子智能体 │ │
│ │ 独立上下文,不占主对话空间 │ │
│ │ Haiku 模型,快速低成本 │ │
│ │ 只返回摘要 │ │
│ └─────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 可选扩展 │
│ │ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Layer 4: 社区扩展(MCP/Plugin) │ │
│ │ ast-grep(AST 搜索)、Serena(符号导航) │ │
│ │ grepai(语义搜索) │ │
│ └─────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
核心哲学: 用模型的推理能力替代索引的定位能力
用 ripgrep 的原始速度保证搜索效率
用 Agent Loop 的多轮机制实现渐进式精确定位Q8: Claude Code 是如何实现"用户选中内容"而非输出内容的能力的?
这个问题可以拆解为:用户在编辑器中选中一段代码,Claude 是怎么"看到"的? 这个过程涉及三层机制:IDE 捕获选中 → 注入到消息上下文 → 模型感知并响应。
1. 两种运行模式的差异
Claude Code 运行在两种模式下,获取用户选中内容的方式不同:
| 模式 | 能否看到用户选中? | 机制 |
|---|---|---|
| 纯 CLI 模式(终端中运行) | 不能自动看到 | 需要用户手动 @ 引用文件,或用管道传入 |
| IDE 集成模式(VS Code/Cursor 扩展) | 能自动看到 | IDE 扩展捕获选中内容,自动注入到上下文 |
2. IDE 模式:选中内容是如何到达模型的
2.1 整体数据流
用户在编辑器中选中一段代码
│
▼
IDE 扩展捕获选中信息
├── 文件路径: src/auth.ts
├── 选中行范围: 第 15-25 行
├── 选中文本内容: "function login(user) { ... }"
├── 光标位置: 第 20 行
│
▼
打包为结构化元数据,注入到发送给模型的消息中
│
├── <open_and_recently_viewed_files> 块
│ → 当前打开的文件、最近查看的文件列表
│
├── <attached_files> 块
│ └── <code_selection path="src/auth.ts" lines="15-25">
│ → 选中的具体代码内容
│
└── 用户的实际输入文本
│
▼
Claude 模型收到的完整消息结构:
[系统提示]
+ [open_and_recently_viewed_files 元数据]
+ [attached_files / code_selection 内容]
+ [用户文本输入]2.2 注入的元数据格式
IDE 扩展自动在用户消息前附加以下结构化信息:
打开和最近查看的文件列表:
xml
<open_and_recently_viewed_files>
Recently viewed files (recent at the top, oldest at the bottom):
- /data/workspace/src/auth.ts (total lines: 150)
- /data/workspace/src/config.ts (total lines: 80)
- /data/workspace/tests/auth.test.ts (total lines: 200)
Files that are currently open and visible in the user's IDE:
- /data/workspace/src/auth.ts (currently focused file, cursor is on line 20, total lines: 150)
</open_and_recently_viewed_files>选中的代码内容:
xml
<attached_files>
<code_selection path="/data/workspace/src/auth.ts" lines="15-25">
15|export async function login(user: string, password: string) {
16| const hash = await bcrypt.hash(password, 10);
17| const record = await db.users.findOne({ user });
18| if (!record) {
19| throw new AuthError('User not found');
20| }
21| if (!await bcrypt.compare(password, record.hash)) {
22| throw new AuthError('Invalid password');
23| }
24| return createSession(record.id);
25|}
</code_selection>
</attached_files>注意:代码内容包含行号前缀(15|),这让模型知道代码在文件中的精确位置。
2.3 模型如何理解这些元数据
模型的系统提示中包含类似以下的指引:
用户消息可能附带以下自动上下文:
- <open_and_recently_viewed_files>: 用户当前打开的文件和最近查看的文件
- <attached_files> 中的 <code_selection>: 用户在编辑器中选中的代码片段
这些信息可能与当前任务相关,也可能不相关。如果需要获取文件内容,请使用 Read 工具。所以模型能够区分"用户输入的文本"和"IDE 自动注入的上下文",并据此判断用户意图。例如:
用户选中了 login 函数 + 输入 "这个函数有什么安全问题?"
→ 模型看到 code_selection 中的 login 函数代码
→ 结合用户问题,分析该函数的安全隐患3. CLI 模式:用户如何引用内容
纯 CLI 模式没有 IDE 扩展帮忙自动捕获选中,用户需要主动引用:
3.1 @ 引用文件
> 解释 @src/auth.ts 中的 login 函数
↓ Claude Code 内部处理:
1. 解析 @src/auth.ts 为文件引用
2. 自动 Read 该文件内容
3. 将内容注入到对话上下文中
4. 模型基于文件内容回答问题@ 引用支持:
| 引用方式 | 示例 | 说明 |
|---|---|---|
| 文件名 | @auth.ts | 模糊匹配,自动补全 |
| 路径 | @src/auth/login.ts | 精确路径 |
| 带行号 | @auth.ts#15-25 | 指定行范围(IDE 模式中 Option+K 自动生成) |
| 文件夹 | @src/components/ | 引用整个目录(尾部加 /) |
| 子智能体 | @"code-reviewer (agent)" | 指定子智能体执行 |
3.2 管道模式
bash
# 将命令输出作为上下文
git diff | claude "审查这些变更"
# 将文件内容作为上下文
cat error.log | claude "分析这个错误"
# 将文件列表作为上下文
find . -name "*.ts" | head -20 | claude "这个项目的结构是什么"管道内容被注入为用户消息的一部分,模型可以直接看到。
3.3 ! 前缀执行命令
! git status
→ 直接执行命令,输出注入到对话上下文
→ 不需要 Claude 审批
→ 相当于"让 Claude 看到这个命令的输出"3.4 粘贴图片
Ctrl+V → 粘贴剪贴板中的图片
→ 显示为 [Image #N] 标记
→ 图片以 base64 编码注入到消息中
→ 模型可以看到和分析图片内容4. IDE 扩展的关键快捷键
| 快捷键 | 平台 | 功能 |
|---|---|---|
Option+K / Alt+K | Mac / Win·Linux | 将当前选中的代码插入为 @file.ts#5-10 引用 |
Cmd+Esc / Ctrl+Esc | Mac / Win·Linux | 在编辑器和 Claude 输入框之间切换焦点 |
Shift+拖拽 | 通用 | 从文件资源管理器拖拽文件到输入框作为附件 |
| 选中代码后直接提问 | 通用 | Claude 自动看到选中的文本(无需额外操作) |
输入框底部有一个选中指示器,显示当前选中了多少行。点击可以切换是否让 Claude 看到选中内容(眼睛/禁眼图标)。
5. 技术实现原理
5.1 IDE 扩展 → CLI 的通信
VS Code 扩展本质上是 CLI 的 GUI 封装。扩展通过以下方式将上下文传递给底层 CLI 进程:
VS Code 扩展
│
├── 监听编辑器事件:
│ • onDidChangeTextEditorSelection → 捕获选中变化
│ • onDidChangeActiveTextEditor → 捕获当前文件切换
│ • onDidOpenTextDocument → 捕获文件打开
│
├── 收集上下文信息:
│ • 当前文件路径 + 光标位置
│ • 选中的文本范围和内容
│ • 最近打开/查看的文件列表
│ • 当前打开的所有编辑器标签
│
└── 注入到发送给 CLI/API 的消息中:
• 作为用户消息的前缀元数据
• 模型看到的是一条包含代码上下文的完整消息5.2 为什么是"注入元数据"而非"调用工具"
一个关键的设计选择:选中内容是作为消息上下文注入的,而不是让模型先调用 Read 工具去读文件。
| 方式 | 优点 | 缺点 |
|---|---|---|
| 注入元数据(实际方案) | 零延迟,模型第一轮就能看到选中内容 | 即使不相关也占用上下文 |
| 工具调用 | 按需读取,不浪费上下文 | 需要额外一轮 API 调用,增加延迟 |
系统提示中有提醒:"这些文件可能与当前对话相关,也可能不相关"——让模型自己判断是否需要关注这些上下文。
5.3 行号的双重作用
注入的代码包含行号前缀(如 15|export async function login...),这有两个作用:
- 模型定位:模型知道代码在文件中的精确位置,如果需要编辑可以用 Edit 工具精准定位
- 系统提示指引:系统提示中说明"行号是元数据,不是代码的一部分",防止模型在生成代码时也带上行号
<inline_line_numbers>
代码块中可能包含 LINE_NUMBER|LINE_CONTENT 格式的行号。
将 LINE_NUMBER| 前缀视为元数据,不要视为实际代码的一部分。
</inline_line_numbers>6. 总结
"用户选中内容"的实现本质:
不是模型主动"看到"用户屏幕
↓
而是 IDE 扩展监听编辑器事件
↓
将选中信息打包为结构化元数据
↓
作为用户消息的前缀注入到 API 请求中
↓
模型从消息中解析出选中内容
↓
结合用户的文本问题进行响应核心机制非常简单——IDE 扩展充当了用户视觉状态到文本上下文的翻译器。模型本身没有"看屏幕"的能力,一切都是通过文本注入实现的。
这也是为什么纯 CLI 模式下需要用 @ 手动引用——没有 IDE 扩展帮你自动捕获,用户必须自己把上下文"告诉" Claude。