目录概要
- 接着上一篇的尾巴
- 一段 80 行 markdown 是怎么变成 Claude 行为的
- Frontmatter 13 字段全景:能开关什么
- 三种动态内容注入:
!、@、$ARGUMENTS - paths 字段:把 command 钉在文件类型上
- context: fork——让 command 在隔离上下文里跑
- 官方 65 个命令的分布与分级
- 必须背下来的 10 个命令
- 写自己的 command 的三种典型形态
- 容易搞错的几个细节
1. 接着上一篇的尾巴
上一篇把 release-notes 生成器拆完,最后留下一句"下一篇把 Commands 单独拎出来细讲"——这一篇就还这个债。
但还之前要先解释一件事:为什么 Command 这么"简单"的东西值得单开一篇。
Command 在三者里看似最朴素——一个 markdown 文件、一段提示词、一个
/name触发。从功能维度看,它就是个 prompt template,比 textexpander 强不了多少。
可是当你真的用它写工作流,会发现 Command 一半的能力都藏在它如何把外部世界拼接进提示词这件事上:
!shell-command`` 把命令输出灌进来@filepath把文件内容灌进来$ARGUMENTS把用户参数拼进来paths让它只对某些文件激活context: fork让它去开子上下文
这些字段每一个都不复杂,组合起来却让 Command 从"按钮"变成"参数化的工作流模板"。这是上一篇没展开的部分。
资深架构师看 Command 的视角,应该跟看 Makefile target 或者 shell script with frontmatter 一样——它是一种 declarative pipeline 的入口描述符,而不是一段聊天提示。这一篇就按这个视角拆。
2. 一段 80 行 markdown 是怎么变成 Claude 行为的
先把 Command 的执行链路画出来。理解了链路再看字段,每个字段在哪一步起作用就一目了然。
sequenceDiagram
actor User as 用户
participant CLI as claude CLI
participant Loader as Command Loader
participant Tools as 内置工具<br/>(Bash/Read/...)
participant Model as Claude 模型
participant Tool2 as 模型调度的工具
User->>CLI: 输入 /release-notes-crafter
CLI->>Loader: 解析 / 触发
Loader->>Loader: 1. 读 .claude/commands/release-notes-crafter.md
Loader->>Loader: 2. 解析 frontmatter
Loader->>Tools: 3. 执行 ! 块 (shell)
Tools-->>Loader: stdout 结果
Loader->>Loader: 4. 内联 @ 文件引用
Loader->>Loader: 5. 替换 $ARGUMENTS / $0..$N
Loader->>Model: 拼好的最终 prompt + frontmatter (model/effort/allowed-tools)
Model->>Tool2: 按 prompt 指示调用 AskUserQuestion / Agent / Skill
Tool2-->>Model: 工具结果
Model-->>User: 最终响应
这条链路里有四个时机要分清:
| 阶段 | 谁干 | 关键动作 |
|---|---|---|
| 加载 | Loader | 读取文件、解析 frontmatter |
| 预处理 | Loader | 执行 !、内联 @、替换 $ARGUMENTS |
| 注入 | CLI | 把整段提示词作为用户消息塞进当前会话 |
| 执行 | Model | 按提示词调度工具 |
重点:
!和@是在 Loader 阶段处理的,不是模型在执行。这个分工决定了你能用!git status`` 拿到一份准确的、与模型无关的现场快照——如果让模型自己Bash("git status"),结果是"模型解读后的描述",不一样的。
这条规则很多人不知道,写多几个 command 会自己悟出来。我自己是写到第 5 个 command 时,被一个"为什么拿不到 PR diff"的莫名错误点醒的——模型自己跑 Bash("gh pr view") 的输出,跟 Loader 跑 !`gh pr view` 的输出,在某些字段(ANSI color code、别名展开)上就是不一样。早知道这一条,可以省我两小时调试。
3. Frontmatter 13 字段全景:能开关什么
Command 的 frontmatter 在最新版(v2.1.96)有 13 个字段。完整 YAML 示意:
---
name: my-command # 显示名 + /slash 标识,缺省取目录/文件名
description: 做某件有用的事 # 自动补全 + 语义匹配(如果允许)
argument-hint: [issue-number] # 自动补全时的参数 placeholder
disable-model-invocation: false # 是否禁止模型自动触发
user-invocable: true # 是否在 / 菜单出现
paths: "src/**/*.ts" # glob,命中文件才激活(可逗号分隔或列表)
allowed-tools: Read, Edit, Bash(gh *) # 激活时不需要权限确认的工具
model: sonnet # 运行时模型
effort: high # 运行时 effort 等级
context: fork # 设为 fork 则跑在独立子上下文
agent: general-purpose # context: fork 时的子 agent 类型
shell: bash # !`cmd` 块用什么 shell(bash/powershell)
hooks: # 仅作用于本 command 的 lifecycle hooks
PostToolUse:
- matcher: "Edit"
hooks: [...]
---
13 个字段不是平铺的,按"控制什么"可以分成五类:
mindmap
root((Command 13 字段))
身份
name
description
argument-hint
可见性与触发
user-invocable
disable-model-invocation
paths
运行时资源
model
effort
allowed-tools
上下文边界
context
agent
动态行为
shell
hooks
下面挑几个容易踩雷的单独说。
3.1 name 缺省、显式哪个对?
name 缺省取目录或文件名——.claude/commands/release-notes-crafter.md 没写 name,slash 自动就是 /release-notes-crafter。正常情况省着写就好,只在 plugin marketplace 命名冲突、或中文目录名撞 unicode 时才显式覆盖。
真正不可省的字段是 description——它直接影响自动补全是否展示和模型是否能匹配。
3.2 description 决定生死
---
description: 提交并 push 当前分支,再开 PR
---
description 是用户在 / 菜单里看到的一行字,也是模型语义匹配(如果 disable-model-invocation 没关)的依据。没写 description 的 command 实际上是"半盲"状态——用户能 /name 触发,但永远不会被自动建议。
写法上有两个常见反模式:
| 反模式 | 例子 | 问题 |
|---|---|---|
| 太短 | description: commit | 用户看不懂、模型匹配不准 |
| 太长 | description: A comprehensive workflow that takes... | 占自动补全空间,反而不想点 |
折中是一个动词 + 一个目标 + 一个约束:"提交并 push 当前分支并开 PR"——动词"提交+push+开",目标"PR",约束"当前分支"。
3.3 disable-model-invocation:把按钮锁死成"只能手按"
默认情况下,Command 的 description 也会进入模型的可调用列表。但 Command 的设计哲学是"用户主动触发的入口"——上一篇的对比表里写过这点。所以很多团队会把所有 commands 默认设置 disable-model-invocation: true:
---
description: 提交并开 PR
disable-model-invocation: true
---
效果:
- 用户输入
/commit-and-pr,正常触发 - 用户说"帮我提交并开 PR",模型不会主动选这个 command(会自己用 Bash + gh 做)
这个开关跟 user-invocable: false 是一对镜像:
| 字段 | 用户能 / 触发? | 模型能自动触发? | |
|---|---|---|---|
| 默认 | ✅ | ✅ | |
user-invocable: false | ❌ | ✅ | |
disable-model-invocation: true | ✅ | ❌ | |
| 都开 | ❌ | ❌ | (这种一般是 paths 触发的"知识背景") |
四象限互不矛盾,看需求选。
一个反问——Command 明明定位是"用户主动触发的入口",为什么
disable-model-invocation默认偏偏是false?因为 Anthropic 不想让你写完一个 command 之后发现"模型不认你",所以默认全开。代价是团队里每多一个人,就多一份"我没按/pr-review,Claude 怎么自己开了?"的困惑。成熟团队的做法是上来就把这个字段默认true,省心。
3.4 allowed-tools:本地小作用域的权限控制
allowed-tools: Read, Edit, Bash(gh *)
这里写的工具,在这个 command 激活期间不会触发权限确认。语法支持精确到子命令:
Bash(gh *):只放行gh开头的 bash 命令Bash(npm test):只放行字面npm testBash:所有 Bash(最宽,少用)Read、Edit、Write:纯工具名
allowed-tools 是叠加在全局 settings 之上的本地白名单。它不会扩展全局 deny 规则——deny 永远赢。
3.5 model 和 effort:分层用模型省钱
上一篇讲过 release-notes-crafter 用 model: haiku 是因为 command 只编排不算账。这里再补一刀:
---
description: 起草 release notes 草稿
model: opus
effort: high
---
某些写作类 command 反过来——内容质量重要,让 opus 高 effort 上。effort 等级 low/medium/high/max 互相是 token 消耗倍数关系(max 大概是 low 的 8x,按官方近似),所以默认值不写就好,特殊场景才显式拉满。
这是一条实用经验——default everything,遇到具体痛点再覆盖。Command 模板不要写得跟生产配置一样事无巨细。
4. 三种动态内容注入:!、@、$ARGUMENTS
这是 Command 区别于"普通提示词"的地方。三种注入分别处理"shell 输出、文件内容、用户参数"。
4.1 !shell-command`` —— shell 输出注入
---
description: 提交工作流——基于当前 diff 生成 commit message
allowed-tools: Bash(git *), Read, Edit
---
# 提交工作流
## 当前 git status
!`git status --short`
## 暂存区 diff
!`git diff --staged`
## 最近 5 次提交
!`git log --oneline -5`
请基于以上信息:
1. 写一条 conventional commits 风格的 commit message
2. 展示给我确认
3. 确认后执行 git commit
加载这个 command 时,三个 ! 块会被先执行,stdout 替换原位置,再把整段塞给模型。模型看到的是已经填好的现场,省了它再去 Bash 一遍。
注意:
- 反引号是 backtick,不是单引号。容易在 markdown 编辑器里被自动替换
!块里只能跑一行,多行用&&连接- 如果命令报错,stderr 也会带进来——可以靠这个调试
shell: powershell时语法变成 PowerShell 表达式!块的输出不计入 allowed-tools 检查,它是 Loader 阶段执行的
一个常见错误是把交互式命令放进去:!vim file.md`` 会让 Loader 卡住。Loader 期待的是"跑完即返回 stdout"的一次性命令。
4.2 @filepath —— 文件内容注入
---
description: 根据规范 review 当前 PR
---
请按照以下规范检查暂存改动:
@docs/CODE_REVIEW_CHECKLIST.md
当前改动:
!`git diff origin/main`
@ 在 Loader 阶段把文件逐字内联进 prompt——不是让模型去 Read,是预先填好。
高级工程师立刻会问的几件事——
- 大文件会爆 context 吗? 会。
@不截断、不压缩、不做 summary,100KB 的规范原样塞进来,token 全计费。大文件让模型 Read——它读到一半发现不相关会自己退出;@适合 <10KB 的稳定规范文件。 - 路径怎么解析? 相对路径基准是当前工作目录(CWD),不是 command 文件自己的位置。这跟 CLAUDE.md 的
@import语义一致——记错的人很多。 - 跟
Read比真正省了什么? 不是 token(进了 prompt 都要算),是那一轮 tool call 的延迟,再加上"模型先读再决定怎么用"这层认知步骤。对每次都要读、都按同样方式理解的规范类文件——直接@内联,模型的注意力从第一 token 就落在规范上。 - 能不能用通配符? 单个
@只接受一个路径。想拉多个文件就写多条@行;想动态展开走!`cat src/**/*.ts`之类的 shell 路径。
@ 和 ! 的分工到这里就清楚了——规范用 @(稳定、反复引用),现场用 !(动态、每次不同)。
4.3 $ARGUMENTS 和 $0..$N —— 参数
最后一种是用户传参。
---
description: 详细 review 一个 PR
argument-hint: <pr-number> [reviewer]
---
# Review PR #$0
主审人:$1
详情:
!`gh pr view $0 --json title,body,files`
按以上规范展开 review,并把发现 @ 给 $1。
调用:
> /pr-review 1234 alice
替换规则:
| 占位符 | 含义 |
|---|---|
$ARGUMENTS | 整个参数串原样("1234 alice") |
$0 | 第 1 个参数 |
$1 | 第 2 个参数 |
$N | 第 N+1 个参数 |
跟 shell 的
$0是文件名不同,Command 的$0就是第 1 个参数——这是 Loader 自定的语义,别用 shell 直觉去套。
4.4 三者组合的威力
把三种注入叠起来,一个 command 就能做到:
graph LR
U[用户 /pr-review 1234] --> L[Loader]
L -->|"$0=1234"| P1[替换参数]
L -->|"!`gh pr view 1234`"| P2[拉 PR 元信息]
L -->|"@CHECKLIST.md"| P3[内联规范]
P1 --> M[最终 prompt]
P2 --> M
P3 --> M
M --> Claude[模型执行 review]
style L fill:#ff9
style M fill:#afa
这是为什么 Command 不只是"按钮"——它是一个会自己去抓现场的按钮。Makefile 比起"双击 .sh 脚本"的优势在哪?就在 $(shell ...) 这种东西。Command 的 !/@/$ARGUMENTS 是同一个意思。
5. paths 字段:把 command 钉在文件类型上
paths 是个低调但很有用的字段:
---
description: 检查 React 组件的 a11y
paths:
- "src/components/**/*.tsx"
- "src/pages/**/*.tsx"
---
效果是:当当前对话焦点文件匹配 paths 时,这个 command 才进入 Claude 的可用列表。其他时候它就是隐形的。
跟 disable-model-invocation 不同:
disable-model-invocation: true—— 永远不让模型自动选paths: "..."—— 只在工作上下文相关时才候选
paths 模式让你给一个仓库写几十个超细分的 command 也不会污染补全。React 组件场景才出现 React 相关 command,写 SQL 时它们隐身。
跟 Skill 的 paths 字段是一回事,跟 Skill 那边的设计共享同一套 glob 匹配引擎。
6. context: fork——让 command 在隔离上下文里跑
最后一个值得展开的字段是 context: fork:
---
description: 跨整个 monorepo 找出所有未使用的依赖
context: fork
agent: general-purpose
allowed-tools: Read, Glob, Grep, Bash(npm *)
---
# 找出未使用依赖
1. 找所有 package.json
2. 对比每个的 dependencies 和 src 引用
3. 列出未引用的依赖
加上 context: fork 之后,这个 command 不再"内联到主对话"——它跑在一个临时子上下文里,主对话只看到最终结果。
graph TB
subgraph Main[主对话]
M1[用户消息]
M2[Claude 响应]
Trig[/find-unused-deps 触发]
R[只看到最终结果<br/>'5 个未使用依赖:A B C D E']
end
subgraph Forked[fork 出的临时上下文]
F1[读 100 个 package.json]
F2[grep src 引用]
F3[计算差集]
end
Trig -.fork.-> Forked
Forked -.返回结果.-> R
style Forked fill:#fcc
style R fill:#cfc
这就把 Command 升级成了"带 UI 的 Subagent 入口"——用户主动触发、行为像 agent。
什么时候用?凡是这个 command 内部要读大量文件、做大量推理、产生大量中间输出的场景:
- monorepo 全局扫描
- 跨整个仓库的统计
- 跑一遍所有测试并归类失败原因
不用 context: fork 的话,几百个文件读完会撑爆主对话。
7. 官方 65 个命令的分布与分级
讲完自定义 command,看一眼"官方已经准备好的"。Claude Code v2.1.96 内置 65 个 slash command,按功能聚类:
mindmap
root((65 个内置 10 大类))
上下文管理 最值钱
/context
/compact
/clear
模型控制
/plan
/model
/effort
会话管理
/rename
/resume
/rewind
/btw
扩展管理
/agents
/skills
/mcp
/memory
调试兜底
/doctor
/diff
其他 5 类 一年两次
认证 / 配置
项目 / 远程 / 导出
65 个全记得完吗?记不完,也不需要——其中 5 个分类(认证、配置、项目管理、远程、导出)一年用不上两次。完整清单查官方的 Slash Commands 文档 就行。下面挑架构师工作流里影响最大的 5 个分类详细说,就是 mindmap 上半截那五支。
7.1 上下文管理类——最值钱的 7 个
| Command | 实战价值 |
|---|---|
/context | 用色块可视化看上下文是什么"塞满的"——Read 占多少、Tool 输出占多少。看完就知道下一步该 /compact 哪部分 |
/compact [instructions] | 手动压缩对话,可带 instructions 指导压缩重点。50% 上下文是 Boris 推荐的手动 compact 时机 |
/clear | 直接清空。/reset、/new 是 alias |
/cost | 看本会话消耗了多少钱 |
/usage | 看当前套餐还剩多少额度 |
/insights | 跑一份分析报告,看你最常用的工具、最花时间的环节 |
/stats | 日常使用统计图 |
/compact 的 instructions 参数被严重低估。例子:
/compact 保留所有对架构决策的讨论,删除工具输出
压缩后剩下的就是决策脉络,工具反复读文件的 token 全没了。在长对话里这一招经常救命。
7.2 模型控制——架构师最该熟的几个
| Command | 实战价值 |
|---|---|
/plan | 进入 plan mode——只读不写,强制 Claude 先出方案再执行 |
/ultraplan <prompt> | 在云端起草计划,浏览器里审,再回到本地执行 |
/model | 切换模型,左右箭头调 effort |
/effort | 直接改 effort 不换模型 |
/fast | fast mode 切换 |
复杂任务的标准流程——/plan 进 plan mode → 跟 Claude 来回打磨方案 → 满意了退出 plan mode → 开干。这个流程可以避免 Claude "上来就动手"造成的浪费。
7.3 会话管理——多线程开发利器
| Command | 实战价值 |
|---|---|
/rename [name] | 给会话取个有语义的名字,列表里能找到 |
/resume [session] | 恢复指定会话;不带参数则进 picker |
/rewind | 回退到任意之前的 checkpoint,包括代码!别名 /checkpoint |
/branch | 从当前点 fork 一份对话——同一个起点试两条路径 |
/btw <question> | "顺便问一下"——临时旁支问题不污染主线 |
/rewind 是 Claude Code 比传统 IDE 更强的地方——不仅会话能回退,代码也能回到那一刻。Claude 跑偏了不要修修补补,rewind 到岔路口重选方向比一行行 revert 干净。
7.4 扩展类——管你写的所有东西
| Command | 实战价值 |
|---|---|
/agents | 管理你的 subagents |
/skills | 列出可用的 skills |
/hooks | 看 hook 配置(不能改,要改去 settings.json) |
/mcp | 管 MCP 服务器连接和 OAuth |
/plugin | 管插件 |
/memory | 编辑 CLAUDE.md,开关 auto-memory |
/reload-plugins | 不重启进程加载新插件改动 |
后面 06、07、08、10 篇会分别深入这些,这里只是认门牌号。
8. 必须背下来的 10 个命令
如果只能记 10 个,按重要度排序如下:
| 优先 | Command | 一句话用法 |
|---|---|---|
| 1 | /compact | 上下文 50% 时手动压缩,永远比让它自动压缩聪明 |
| 2 | /plan | 复杂任务永远先 plan mode 谈方案 |
| 3 | /rewind | Claude 跑偏立刻回退,别在烂上下文里继续修 |
| 4 | /model | 切换模型/effort——plan 用 opus、coding 用 sonnet 是基操 |
| 5 | /context | 看上下文构成,决定怎么 compact |
| 6 | /permissions | 把常用工具预批准,比 --dangerously-skip-permissions 安全得多 |
| 7 | /resume | 重要会话先 /rename,之后能 /resume 找回 |
| 8 | /clear | 真的不需要历史就清,不要让上下文涨着浪费钱 |
| 9 | /doctor | 任何"诶它怎么不工作"先跑一下 |
| 10 | /diff | 看 Claude 改了啥,比直接看 git diff 更带"per turn"维度 |
这 10 个有 7 个是上下文/会话/模型管理类——这不是巧合。LLM agent 工具的瓶颈从来不在能力,而在上下文管理。十年传统软件经验上来用 Claude Code,最容易踩的坑就是把它当成"无限上下文的助手",不停喂它信息——结果越用越笨,token 越烧越多。10 个命令里 7 个是治这个病的。
9. 写自己的 command 的三种典型形态
最后回到实战。Command 在实际项目里通常长成三种形态:
形态一:纯模板型(最简单)
---
description: 把 React 组件改成 hooks 写法
---
把当前选中的 React 组件从 class 写法改成 functional + hooks。
保持 props 接口、保持测试通过、保持 PropTypes。
零字段,纯 prompt。适合重复出现的"按规范做某件事"。
形态二:工作流编排型(最常见)
上一篇的 release-notes-crafter.md 就是这种:
---
description: Craft release notes for a given version — fetches commits between tags, categorizes them, writes a polished RELEASE_NOTES.md
model: haiku
---
正文用结构化 markdown 描述 step by step:哪步用 AskUserQuestion、哪步 Agent、哪步 Skill。Command 不自己干活,调度其他扩展。
形态三:现场快照型(带 ! 和 @)
---
description: 提交并 PR
allowed-tools: Bash(git *), Bash(gh *)
---
# 当前现场
!`git status --short`
# 暂存改动
!`git diff --staged`
# 项目 commit 规范
@docs/COMMIT_CONVENTIONS.md
请基于现场和规范,生成 commit message 并提交,然后 gh pr create。
把"上下文准备"自动化掉,模型来了就有完整快照。
关键洞察:上面三种形态的复杂度是递增的,但通用价值也是递增的。形态一基本可以被任何 LLM 助手替代;形态三才是 Claude Code 的护城河——它是真正利用了"CLI 工具"这个身份的。
写第一个 command 时大家都从形态一开始(容易),但真正起作用的 command 大多是形态二和形态三。
10. 容易搞错的几个细节
最后列几条踩过的坑,专挑文档里没强调的:
Backtick 不是单引号
!`git status` 用的是反引号(U+0060),不是单引号(U+0027)。VS Code 默认的 markdown 智能引号会把它偷换成 curly quote,结果命令不执行、没有报错、Loader 直接把那一行当成纯文本塞给模型。这个设计谁拍板的该拉出去面壁——连个 warning 都没有。
$ARGUMENTS 不会做 shell escape
如果用户传入的参数里有 ; | &,原样替换进 ! 块就是命令注入。永远不要把 $ARGUMENTS 直接拼到 ! 块里,要用就拼到自然语言提示里让模型决定怎么用。
paths 是匹配"工作焦点",不是匹配"会话历史"
测试 paths 是否生效:用 /add-dir 加目录,让你的 command 出现在补全里。光打开一个 tab 不算"工作焦点"。
context: fork 不能套娃
fork 出的子上下文里,子 agent 不能再启动一个 fork。设计上是有意的——避免无限递归。
hooks 字段的 matcher 是工具名,不是字符串
hooks:
PostToolUse:
- matcher: "Edit" # 正确:匹配 Edit 工具
- matcher: "edit*" # 错误:不会匹配
完整 matcher 语法在第 07 篇 hooks 系统里展开。
写到这里
Command 这个看起来最朴素的扩展点,背后其实有一整套"prompt 编译期"——! 跑 shell、@ 内联文件、$ARGUMENTS 拼参数、paths 限定激活范围、context: fork 切换执行模式。这些字段的组合,让一个 markdown 文件变成了会自动抓现场的工作流入口。
13 个 frontmatter 字段、3 种动态注入语法、65 个内置 command——这些细节背下来意义不大,真正有用的是知道哪个能力对应哪个字段。下次想"我要让一个 command 在 .ts 文件里才出现",能立刻定位到 paths,就够了。
Skills 是这趟旅程下一站。Command 是按钮,按下去执行;Skill 是工具箱里的工具,模型自己判断什么时候开柜子拿哪一把。下一篇把 Skill 的 9 种类型、渐进式揭露的设计哲学、Skill 工具的调用机制全部摊开讲。