作者:互联网 时间: 2026-07-01 09:25:06
!、@、$ARGUMENTS上一篇把 release-notes 生成器拆完,最后留下一句"下一篇把 Commands 单独拎出来细讲"——这一篇就还这个债。

但还之前要先解释一件事:为什么 Command 这么"简单"的东西值得单开一篇。
可是当你真的用它写工作流,会发现 Command 一半的能力都藏在它如何把外部世界拼接进提示词这件事上:
!shell-command`` 把命令输出灌进来@filepath 把文件内容灌进来$ARGUMENTS 把用户参数拼进来paths 让它只对某些文件激活context: fork 让它去开子上下文这些字段每一个都不复杂,组合起来却让 Command 从"按钮"变成"参数化的工作流模板"。这是上一篇没展开的部分。
资深架构师看 Command 的视角,应该跟看 Makefile target 或者 shell script with frontmatter 一样——它是一种 declarative pipeline 的入口描述符,而不是一段聊天提示。这一篇就按这个视角拆。
先把 Command 的执行链路画出来。理解了链路再看字段,每个字段在哪一步起作用就一目了然。
sequenceDiagramactor User as 用户participant CLI as claude CLIparticipant Loader as Command Loaderparticipant Tools as 内置工具<br/>(Bash/Read/...)participant Model as Claude 模型participant Tool2 as 模型调度的工具User->>CLI: 输入 /release-notes-crafterCLI->>Loader: 解析 / 触发Loader->>Loader: 1. 读 .claude/commands/release-notes-crafter.mdLoader->>Loader: 2. 解析 frontmatterLoader->>Tools: 3. 执行 ! 块 (shell)Tools-->>Loader: stdout 结果Loader->>Loader: 4. 内联 @ 文件引用Loader->>Loader: 5. 替换 $ARGUMENTS / $0..$NLoader->>Model: 拼好的最终 prompt + frontmatter (model/effort/allowed-tools)Model->>Tool2: 按 prompt 指示调用 AskUserQuestion / Agent / SkillTool2-->>Model: 工具结果Model-->>User: 最终响应
这条链路里有四个时机要分清:
| 阶段 | 谁干 | 关键动作 |
|---|---|---|
| 加载 | Loader | 读取文件、解析 frontmatter |
| 预处理 | Loader | 执行 !、内联 @、替换 $ARGUMENTS |
| 注入 | CLI | 把整段提示词作为用户消息塞进当前会话 |
| 执行 | Model | 按提示词调度工具 |
这条规则很多人不知道,写多几个 command 会自己悟出来。我自己是写到第 5 个 command 时,被一个"为什么拿不到 PR diff"的莫名错误点醒的——模型自己跑 Bash("gh pr view") 的输出,跟 Loader 跑 !`gh pr view` 的输出,在某些字段(ANSI color code、别名展开)上就是不一样。早知道这一条,可以省我两小时调试。
Command 的 frontmatter 在最新版(v2.1.96)有 13 个字段。完整 YAML 示意:
name: my-command# 显示名 + /slash 标识,缺省取目录/文件名description: 做某件有用的事# 自动补全 + 语义匹配(如果允许)argument-hint: [issue-number] # 自动补全时的参数 placeholderdisable-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 hooksPostToolUse:- matcher: "Edit"hooks: [...]
13 个字段不是平铺的,按"控制什么"可以分成五类:
mindmaproot((Command 13 字段))身份namedescriptionargument-hint可见性与触发user-invocabledisable-model-invocationpaths运行时资源modeleffortallowed-tools上下文边界contextagent动态行为shellhooks
下面挑几个容易踩雷的单独说。
name 缺省、显式哪个对?name 缺省取目录或文件名——.claude/commands/release-notes-crafter.md 没写 name,slash 自动就是 /release-notes-crafter。正常情况省着写就好,只在 plugin marketplace 命名冲突、或中文目录名撞 unicode 时才显式覆盖。
真正不可省的字段是 description——它直接影响自动补全是否展示和模型是否能匹配。
description 决定生死description: 提交并 push 当前分支,再开 PR
description 是用户在 / 菜单里看到的一行字,也是模型语义匹配(如果 disable-model-invocation 没关)的依据。没写 description 的 command 实际上是"半盲"状态——用户能 /name 触发,但永远不会被自动建议。
写法上有两个常见反模式:
| 反模式 | 例子 | 问题 |
|---|---|---|
| 太短 | description: commit | 用户看不懂、模型匹配不准 |
| 太长 | description: A comprehensive workflow that takes... | 占自动补全空间,反而不想点 |
折中是一个动词 + 一个目标 + 一个约束:"提交并 push 当前分支并开 PR"——动词"提交+push+开",目标"PR",约束"当前分支"。
disable-model-invocation:把按钮锁死成"只能手按"默认情况下,Command 的 description 也会进入模型的可调用列表。但 Command 的设计哲学是"用户主动触发的入口"——上一篇的对比表里写过这点。所以很多团队会把所有 commands 默认设置 disable-model-invocation: true:
description: 提交并开 PRdisable-model-invocation: true
效果:
/commit-and-pr,正常触发这个开关跟 user-invocable: false 是一对镜像:
| 字段 | 用户能 / 触发? | 模型能自动触发? | |
|---|---|---|---|
| 默认 | ✅ | ✅ | |
user-invocable: false | ❌ | ✅ | |
disable-model-invocation: true | ✅ | ❌ | |
| 都开 | ❌ | ❌ | (这种一般是 paths 触发的"知识背景") |
四象限互不矛盾,看需求选。
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 永远赢。
model 和 effort:分层用模型省钱上一篇讲过 release-notes-crafter 用 model: haiku 是因为 command 只编排不算账。这里再补一刀:
description: 起草 release notes 草稿model: opuseffort: high
某些写作类 command 反过来——内容质量重要,让 opus 高 effort 上。effort 等级 low/medium/high/max 互相是 token 消耗倍数关系(max 大概是 low 的 8x,按官方近似),所以默认值不写就好,特殊场景才显式拉满。
!、@、$ARGUMENTS这是 Command 区别于"普通提示词"的地方。三种注入分别处理"shell 输出、文件内容、用户参数"。
!shell-command`` —— shell 输出注入---description: 提交工作流——基于当前 diff 生成 commit messageallowed-tools: Bash(git *), Read, Edit---# 提交工作流## 当前 git status!`git status --short`## 暂存区 diff!`git diff --staged`## 最近 5 次提交!`git log --oneline -5`请基于以上信息:1. 写一条 conventional commits 风格的 commit message2. 展示给我确认3. 确认后执行 git commit
加载这个 command 时,三个 ! 块会被先执行,stdout 替换原位置,再把整段塞给模型。模型看到的是已经填好的现场,省了它再去 Bash 一遍。
注意:
! 块里只能跑一行,多行用 && 连接shell: powershell 时语法变成 PowerShell 表达式! 块的输出不计入 allowed-tools 检查,它是 Loader 阶段执行的一个常见错误是把交互式命令放进去:!vim file.md`` 会让 Loader 卡住。Loader 期待的是"跑完即返回 stdout"的一次性命令。
@filepath —— 文件内容注入---description: 根据规范 review 当前 PR---请按照以下规范检查暂存改动:@docs/CODE_REVIEW_CHECKLIST.md当前改动:!`git diff origin/main`
@ 在 Loader 阶段把文件逐字内联进 prompt——不是让模型去 Read,是预先填好。
高级工程师立刻会问的几件事——
@ 不截断、不压缩、不做 summary,100KB 的规范原样塞进来,token 全计费。大文件让模型 Read——它读到一半发现不相关会自己退出;@ 适合 <10KB 的稳定规范文件。@import 语义一致——记错的人很多。Read 比真正省了什么? 不是 token(进了 prompt 都要算),是那一轮 tool call 的延迟,再加上"模型先读再决定怎么用"这层认知步骤。对每次都要读、都按同样方式理解的规范类文件——直接 @ 内联,模型的注意力从第一 token 就落在规范上。@ 只接受一个路径。想拉多个文件就写多条 @ 行;想动态展开走 !`cat src/**/*.ts` 之类的 shell 路径。@ 和 ! 的分工到这里就清楚了——规范用 @(稳定、反复引用),现场用 !(动态、每次不同)。
$ARGUMENTS 和 $0..$N —— 参数最后一种是用户传参。
---description: 详细 review 一个 PRargument-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 个参数 |
把三种注入叠起来,一个 command 就能做到:
graph LRU[用户 /pr-review 1234] --> L[Loader]L -->|"$0=1234"| P1[替换参数]L -->|"!`gh pr view 1234`"| P2[拉 PR 元信息]L -->|"@CHECKLIST.md"| P3[内联规范]P1 --> M[最终 prompt]P2 --> MP3 --> MM --> Claude[模型执行 review]style L fill:#ff9style M fill:#afa
这是为什么 Command 不只是"按钮"——它是一个会自己去抓现场的按钮。Makefile 比起"双击 .sh 脚本"的优势在哪?就在 $(shell ...) 这种东西。Command 的 !/@/$ARGUMENTS 是同一个意思。
paths 是个低调但很有用的字段:
description: 检查 React 组件的 a11ypaths:- "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 匹配引擎。
最后一个值得展开的字段是 context: fork:
description: 跨整个 monorepo 找出所有未使用的依赖context: forkagent: general-purposeallowed-tools: Read, Glob, Grep, Bash(npm *)# 找出未使用依赖1. 找所有 package.json2. 对比每个的 dependencies 和 src 引用3. 列出未引用的依赖
加上 context: fork 之后,这个 command 不再"内联到主对话"——它跑在一个临时子上下文里,主对话只看到最终结果。
graph TBsubgraph Main[主对话]M1[用户消息]M2[Claude 响应]Trig[/find-unused-deps 触发]R[只看到最终结果<br/>'5 个未使用依赖:A B C D E']endsubgraph Forked[fork 出的临时上下文]F1[读 100 个 package.json]F2[grep src 引用]F3[计算差集]endTrig -.fork.-> ForkedForked -.返回结果.-> Rstyle Forked fill:#fccstyle R fill:#cfc
这就把 Command 升级成了"带 UI 的 Subagent 入口"——用户主动触发、行为像 agent。
什么时候用?凡是这个 command 内部要读大量文件、做大量推理、产生大量中间输出的场景:
不用 context: fork 的话,几百个文件读完会撑爆主对话。
讲完自定义 command,看一眼"官方已经准备好的"。Claude Code v2.1.96 内置 65 个 slash command,按功能聚类:
mindmaproot((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 上半截那五支。
| Command | 实战价值 |
|---|---|
/context | 用色块可视化看上下文是什么"塞满的"——Read 占多少、Tool 输出占多少。看完就知道下一步该 /compact 哪部分 |
/compact [instructions] | 手动压缩对话,可带 instructions 指导压缩重点。50% 上下文是 Boris 推荐的手动 compact 时机 |
/clear | 直接清空。/reset、/new 是 alias |
/cost | 看本会话消耗了多少钱 |
/usage | 看当前套餐还剩多少额度 |
/insights | 跑一份分析报告,看你最常用的工具、最花时间的环节 |
/stats | 日常使用统计图 |
/compact 的 instructions 参数被严重低估。例子:
/compact 保留所有对架构决策的讨论,删除工具输出
压缩后剩下的就是决策脉络,工具反复读文件的 token 全没了。在长对话里这一招经常救命。
| Command | 实战价值 |
|---|---|
/plan | 进入 plan mode——只读不写,强制 Claude 先出方案再执行 |
/ultraplan <prompt> | 在云端起草计划,浏览器里审,再回到本地执行 |
/model | 切换模型,左右箭头调 effort |
/effort | 直接改 effort 不换模型 |
/fast | fast mode 切换 |
复杂任务的标准流程——/plan 进 plan mode → 跟 Claude 来回打磨方案 → 满意了退出 plan mode → 开干。这个流程可以避免 Claude "上来就动手"造成的浪费。
| Command | 实战价值 |
|---|---|
/rename [name] | 给会话取个有语义的名字,列表里能找到 |
/resume [session] | 恢复指定会话;不带参数则进 picker |
/rewind | 回退到任意之前的 checkpoint,包括代码!别名 /checkpoint |
/branch | 从当前点 fork 一份对话——同一个起点试两条路径 |
/btw <question> | "顺便问一下"——临时旁支问题不污染主线 |
/rewind 是 Claude Code 比传统 IDE 更强的地方——不仅会话能回退,代码也能回到那一刻。Claude 跑偏了不要修修补补,rewind 到岔路口重选方向比一行行 revert 干净。
| Command | 实战价值 |
|---|---|
/agents | 管理你的 subagents |
/skills | 列出可用的 skills |
/hooks | 看 hook 配置(不能改,要改去 settings.json) |
/mcp | 管 MCP 服务器连接和 OAuth |
/plugin | 管插件 |
/memory | 编辑 CLAUDE.md,开关 auto-memory |
/reload-plugins | 不重启进程加载新插件改动 |
后面 06、07、08、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 个是治这个病的。
最后回到实战。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.mdmodel: haiku
正文用结构化 markdown 描述 step by step:哪步用 AskUserQuestion、哪步 Agent、哪步 Skill。Command 不自己干活,调度其他扩展。
description: 提交并 PRallowed-tools: Bash(git *), Bash(gh *)# 当前现场!`git status --short`# 暂存改动!`git diff --staged`# 项目 commit 规范@docs/COMMIT_CONVENTIONS.md请基于现场和规范,生成 commit message 并提交,然后 gh pr create。
把"上下文准备"自动化掉,模型来了就有完整快照。
写第一个 command 时大家都从形态一开始(容易),但真正起作用的 command 大多是形态二和形态三。
最后列几条踩过的坑,专挑文档里没强调的:
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 工具的调用机制全部摊开讲。