Claude Code 完全指南——Slash Command 到 Skill 的演进

Published on:

上一篇文章里,我们聊了 Claude Code 的 CLAUDE.md,它告诉我们 Claude Code 在进入一个项目时应该先知道什么,也就是项目背景、协作约束和工作边界该如何被提前说明。不过当这些静态上下文准备好之后,真正进入日常使用,我们很快还会碰到另一个更实际的问题,面对代码评审、提交变更、生成总结这类经常遇到的任务,怎样才能让 Claude Code 每次都用更稳定、更一致的方式进行工作呢?Slash Command 正是为这个问题出现的,它看起来只是一个以 / 开头的命令,背后其实对应的是一套被命名的任务入口和工作流约定,而这也是我们这一篇文章要继续往下展开的主题。

什么是 Slash Command

第一次看到 Slash Command 时,很多人会下意识把它理解成终端里的普通命令,不过在 Claude Code 里,它更准确的身份其实是一个被命名的任务入口。你输入 /clear/compact 这类命令时,调用的是 Claude Code 自带的内置能力,而当你输入 /review/commit/submit-pr 这类团队自己定义的命令时,本质上调用的则是一份预先写好的 prompt 模板。所以从使用体验上看,它像命令,从实现方式上看,它更像一层把 prompt 命名化、结构化、可复用化的封装。

Slash Command 真正有意思的地方,不在于它让你少输入一些信息,而在于它把原本散落在聊天里的临时指令,变成了一个可以被反复调用的稳定入口。你不再需要每次都重新描述:帮我 review 这段代码、按团队规范写 commit message、按固定结构整理 PR 总结等等,而是可以直接把这些动作收敛成一个 /command,让 Claude Code 直接进入对应的工作模式。

个人命令和项目命令

Claude Code 里的自定义 Slash Command,官方主要支持两种作用域,一种是个人命令,放在 ~/.claude/commands/ 下面,另一种是项目命令,放在仓库里的 .claude/commands/ 目录下。两者的区别不在于语法,而在于它们服务的对象不同,个人命令更适合沉淀你自己的长期习惯,比如你经常使用的 review 模板、commit 模板、debug 提示词等,项目命令则更适合放团队共享的工作流,让团队里的每个人都能用同样的方式触发同样的任务。

这两类命令虽然看起来只是存放路径不同,但背后的价值其实很不一样。个人命令解决的是个人效率,你可以把自己常用的 prompt 整理成一组稳定工具,项目命令解决的则是团队一致性,因为它可以随着仓库一起被版本管理,让 command 本身也成为团队协作规范的一部分。换句话说,前者是在沉淀你的使用习惯,后者是在沉淀项目的 AI 工作流。

作用域关系

这里还有一个很容易被误解的点,不要把 Slash Command 的作用域关系,直接套用到上一篇文章CLAUDE.md 的作用域模型上。CLAUDE.md 更像是一套会随着目录层级逐步收束的上下文规则,越靠近当前工作目录,约束通常越具体,优先级也越高,但 Slash Command 不是这套逻辑。如果 ~/.claude/commands/.claude/commands/ 下面存在同名命令,那么最终优先的会是用户级目录里的那个版本,也就是 ~/.claude/commands/ 会覆盖项目级的 .claude/commands/

这意味着,Slash Command 的作用域更像是个人覆盖项目,而不是项目就近覆盖个人。为什么要这样设计呢?这其实是出于安全考虑。项目级的 .claude/commands/ 是提交到 Git 仓库里的,任何有仓库写权限的人都可以修改它,如果反过来让项目级优先,就意味着别人完全可以通过提交一个同名 command,悄悄覆盖你原本在本地使用的个人命令。这其实就是一个潜在的 prompt injection 攻击面,因为恶意 PR 不一定非要改源码,它也可以通过篡改 command 来改变你的工作流入口,让 Claude Code 在你不易察觉的情况下执行另一套任务说明。

什么是 prompt injection? 它指的是通过篡改提示词、指令文件或外部输入,影响模型原本应该执行的任务,在 Claude Code 这种会读取项目文件的工具里,命令文件本身也可能成为攻击面。

Slash Command 是如何工作的

从实现方式上看,一个自定义 Slash Command 通常就是一个 Markdown 文件,文件名会变成命令名,文件内容则定义 Claude Code 在执行这个命令时应该做什么。你可以把它理解成一个带名字的 prompt 文件,调用时直接输入 /command-name [arguments],Claude Code 会读取对应文件,再把你传进去的参数替换到 prompt 里。所以它本质上仍然是一个固定模板,只不过这个模板可以接受运行时参数。现行官方文档已经把这部分说明合并到了 Skill 文档里,不过这些参数替换规则对 .claude/commands/ 依然同样适用。

如果只看定义,这件事还是会有点抽象,下面我们用几个例子来看 Slash Command 到底是怎么工作的。

第一种,不带参数。

这种情况最简单,command 文件里写什么,Claude Code 就按这个任务说明去执行什么。

1
2
3
4
5
6
7
8
9
---
description: Review current working tree changes
---

Review the current uncommitted changes and focus on:

- correctness
- regression risk
- missing tests

在 Claude Code 里执行:

1
/review-current
  • 这个文件如果放在 .claude/commands/review-current.md,对应的命令就是 /review-current
  • 因为没有参数,所以 Claude Code 只需要读取这份固定模板,然后直接执行里面定义的任务
  • 这类 command 适合那些输入结构非常稳定的高频任务,比如 review 当前改动、整理当前会话、检查最近提交等

第二种,带一个参数。

如果一个 command 只需要接收一段完整输入,通常用 $ARGUMENTS 会更直观,因为它表示用户在命令名后面输入的全部内容。

1
2
3
4
5
6
---
description: Check today's weather for a city
---

Show today's weather for $ARGUMENTS.
If the city name is ambiguous, ask for clarification first.

在 Claude Code 里执行:

1
/weather Beijing
  • 在这个例子里,文件路径如果是 .claude/commands/weather.md,对应的命令就是 /weather
  • /weather Beijing 里的 Beijing 会被完整替换进 $ARGUMENTS
  • 这里只有一个参数时,用 $ARGUMENTS 会更加直观,因为它表达的就是命令后的全部参数
  • 这种写法还有一个好处,如果你输入的是 /weather New York,那么 New York 这整段内容都可以被当作一个整体来处理

第三种,带多个参数。

如果一个 command 需要按位置区分不同参数,那么就更适合使用 $ARGUMENTS[N],或者它的简写 $0$1$2

1
2
3
4
5
6
---
description: Migrate a component between frameworks
---

Migrate the $0 component from $1 to $2.
Preserve all existing behavior and tests.

在 Claude Code 里执行:

1
/migrate SearchBar React Vue
  • $0 对应 SearchBar
  • $1 对应 React
  • $2 对应 Vue
  • 这种按位置取值的方式,适合迁移、转换、批量处理这类参数结构明确的任务

官方文档里对这套参数替换规则的说明是这样的,$ARGUMENTS 表示全部参数,$ARGUMENTS[N] 表示按顺序取第几个参数,比如 $ARGUMENTS[0] 是第一个,$ARGUMENTS[1] 是第二个,而 $N 则是 $ARGUMENTS[N] 的简写。还有一个容易忽略的细节,只有当模板里完全没有出现 $ARGUMENTS$0$1 这类参数占位符时,Claude Code 才会把你输入的参数自动追加到内容末尾,只要模板里已经处理过任意一个占位符,那么这些参数就不会再被额外追加一次。

Slash Command 的工作方式其实很直接,就是先用一个固定模板定义任务,再在调用时把参数填进去,它并没有改变 Claude Code 的能力边界,真正改变的是任务的组织方式,原本要在对话里临时说明的事情,现在可以提前写成一个可重复调用的入口。

为什么需要 Slash Command

很多人刚接触 Slash Command 时,都会先问一个问题,我们明明可以直接给 Claude Code 发 prompt 了,为什么还要额外再包一层 /command。如果只是偶尔问一个临时问题,直接对话当然完全够用,但只要你经常让 Claude Code 做重复性的事情,你就会发现,真正麻烦的往往不是任务本身,而是每次都要重新把任务讲清楚

在没有 Slash Command 的时候,我们更常见的交互方式其实是这样的:

1
2
3
review this code
write tests for this function
refactor this module

这种方式的好处是足够灵活,想到什么就可以直接说什么,但一旦任务开始重复出现,很多原本被灵活性掩盖的问题也会慢慢浮出来。Slash Command 正是为了解决这个问题出现的。它做的事情,并不是把 prompt 变短,也不是单纯提供一个更方便的快捷方式,而是把原本零散的 prompt 使用方式,整理成可复用、可预测的任务入口。换句话说,它是在把自由 prompt 逐步变成结构化工作流,这也是 Claude Code 从聊天式使用,走向工程化协作的第一步。

如果把这个问题拆开来看,Slash Command 主要是在处理下面几件事,这也正是它的价值所在。

  • 任务表达不一致:同一个代码评审任务,有人写得很完整,有人只写了一句话,这样使得 Claude Code 每次接收到的都不是相同的任务,所以需要用 command 把 prompt 标准化,让同一类任务有一致的表达
  • 缺少统一任务入口:实际工作中团队里的一些固定任务,如果始终只能靠临时 prompt 来触发,那么每次开始之前都要重新组织一遍语法,团队里也很难沉淀出一套大家都知道该怎么用的固定入口
  • 行为不够可预测:即使大家做的是同一类任务,只要 prompt 写法总在变化,Claude Code 每次理解到的重点就可能不一样,输出自然也容易波动,所以需要用固定 command 把任务描述稳定下来,让结果更加一致

如果再往深层次一点看,Slash Command 还有一个经常被低估的作用,就是它会反过来逼着我们把任务说清楚。很多 prompt 之所以效果忽好忽坏,不一定是模型能力不够,而是我们自己对任务的目标、边界和输出预期并没有想明白。一个 command 一旦要被长期复用,你就很难继续用那种模糊、临时、带着很多默认前提的说法去描述任务,这个整理过程本身,就已经是在提升工作流质量了。

这些问题会让 Claude Code 的表现很容易随着 prompt 写法变化而波动,Slash Command 的作用,就是先把常见任务的表达方式固定下来,让协作变得更加稳定。

Slash Command 常见误区

很多人会对 Slash Command 产生一些额外的期待,但如果不先把它的边界了解清楚,后面就很容易高估它的作用,所以这里有几个常见误区,我们来看一下。

使用 Slash Command 可以减少上下文的大小吗?

这个误区其实很常见,因为从表面上看,原来你可能要在对话框里输入一大段任务说明,现在却只需要打一个 /review/commit 这样的命令,看起来输入明显变短了,于是很容易让人觉得,既然我发给 Claude Code 的只有一个命令,那进入上下文的内容应该也变少了。

先说结论,Slash Command 不能从根本上减少上下文大小,它更多是在优化上下文的组织方式和注入时机。Slash Command 的本质还是 prompt,只不过这段 prompt 被提前写进了一个带名字的模板里,所以它并不会凭空把原本需要说明的内容压缩掉,也不会让同样的信息突然少占一些 token。

它真正改变的,其实是我们和这些上下文打交道的方式。原来你可能每次都要手工输入一段指令,现在这些内容可以被收进 command 模板里,不用反复重写。原来一些说明只能临时写在对话里,现在它们可以在任务开始时被稳定注入。这样一来,使用体验当然会更清爽,但清爽不等于信息量消失了,它只是从散落在聊天里的临时文字,变成了一份更有组织的固定输入。

所以更准确地说,Slash Command 解决的是上下文管理问题,而不是上下文压缩问题。如果一个任务本来就需要很多背景、步骤、约束和输出格式,那么把它写成 Slash Command 之后,这些信息依然还是要进上下文,只是它们进入上下文的方式会更稳定、更整齐一些。

Slash Command 比普通 prompt 更强吗?

这个误区也很容易出现,因为很多人实际用下来都会发现,同样是做代码评审、调试或写总结,某些 Slash Command 的输出确实比临时写的一句 prompt 的效果更好,于是就会自然地把这种差异理解成 Slash Command 本身的能力更强。

但这里真正起作用的,往往不是 Slash Command 这个形式本身,而是背后的任务说明已经被提前整理过了,边界更清楚,步骤更完整,输出要求也更明确。换句话说,看起来像是 command 更强,很多时候其实只是那份 prompt 本来就写得更好。

反过来说,如果一个 Slash Command 里面包的仍然是一段含糊、混乱、缺少边界的 prompt,那它一样不会好用,它不会因为被命名了,就自动变成一个高质量工作流。所以更准确的说法应该是,Slash Command 不是更强的模型能力,而是更强的工作流封装。它不会提升模型能力上限,真正提升的是任务表达的稳定性,以及好 prompt 被重复使用的可能性。

官方的变化:Skill 正在取代 Slash Command

前面我们一直在讲 Slash Command 怎么怎么好,但如果查看 Claude Code 官方现在的文档,就会发现官方对 Slash Command 的看法已经发生了改变,它不再把 Slash Command 单独当成一套独立机制,而是已经把这部分内容合并进 Skill 了。

这不是简单的名称变化,而是官方组织这套能力的方式变了。文档里写得很清楚,custom commands 已经被并入 Skill,一个放在 .claude/commands/deploy.md 里的 command,和一个放在 .claude/skills/deploy/SKILL.md 里的 Skill,最终都可以生成同一个 /deploy 入口。现有的 .claude/commands/ 也依然继续可用,这说明官方并没有直接废弃 Slash Command,而是在把它从一个单独能力,收进更完整的 Skill 体系里。

官方新推出的 Skill,是一套更加完整的组织方式,比如 SKILL.md 作为主入口、支持文件作为补充材料、frontmatter 控制自动调用和权限边界,以及在相关场景下自动加载 Skill 的能力,这些都不是传统单文件 Slash Command 擅长的事情。

Skill 可以在相关场景里自动触发,这件事真正有价值的地方在于,很多任务你并不一定会主动想起要先输入一个命令,比如你刚改完一段鉴权逻辑,Claude Code 如果能自动把安全规则、兼容约束和测试输出一起带进来,整个流程就会自然很多,你也不需要每次都先想一遍这里是不是该手动调用某个工具。相比之下,Slash Command 是主动触发的,什么时候执行完全由你决定,所以当你很明确地知道自己现在就是要跑一个固定流程时,直接输入一次 /command 往往会更合适。

所以现在更值得关注的,已经不是命令长什么样,而是这个命令背后的能力怎么组织,也就是它背后的 Skill 机制。

从 Slash Command 到 Skill:一个实际案例分析

为了把这个变化讲得更清楚,我们可以做一个小实验来进行对比,我们可以放两个几乎一样的工程,一个是 command-demo,用 Slash Command 来审查并修复 auth.ts 文件中的一个安全问题,另一个是 skill-demo,用 Skill 来完成同样的任务。两边的目标都一样,都是读代码、看测试、修复缺陷、跑测试,再给出一份修复报告。

这两个实验工程放的是同一套业务代码和文档,真正的差别主要集中在 .claude/ 和少量补充文件上。

1
2
3
4
5
6
7
8
9
common/
├── auth.ts
├── auth.test.ts
├── scripts/
│ └── latest-test-output.sh
└── docs/
├── security.md
├── compat.md
└── report-template.md

先看 command-demo 独有的部分:

1
2
3
4
command-demo/
└── .claude/
└── commands/
└── review-auth-cmd.md

command-demo 里,所有要求都要写进这个 Slash Command 文件 .claude/commands/review-auth-cmd.md 里。你得在这个文件里一次性告诉 Claude Code,先读 auth.tsauth.test.ts,再去看 docs/security.mddocs/compat.md,最后按 docs/report-template.md 的格式输出结果。这样做当然也能完成任务,这次实验里它最后也确实修好了代码,但问题是,文件一多、约束一多、模板一多,这个 command 很快就会变成一份越来越长、越来越难维护的大 prompt。

再看 skill-demo 独有的部分:

1
2
3
4
5
6
skill-demo/
├── .claude/
│ └── skills/
│ └── review-auth-skill/
│ ├── SKILL.md
│ └── README.md

到了 skill-demo 这边,组织方式就不一样了。SKILL.md 只负责说明主任务,安全规则、兼容约束、报告模板这些内容都可以放在各自独立的文件里,需要时再引用进来,甚至还能通过 !./scripts/latest-test-output.sh 这种方式,把运行前最新的测试输出直接注入到 Skill 里。这样一来,主入口本身不会越来越臃肿,真正会持续变化的内容也有了更自然的落点。

什么是动态上下文注入? 就是在任务开始前,先执行一段命令,把当下最新的脚本输出、构建状态或其他运行结果带进上下文,而不是只依赖提前写死的静态说明。

光看目录结构,这个对比还不太明显,真正让 Claude Code 把两边都跑一遍之后,差异会真正体现出来。先说结果,两边最后都把问题修好了,所有测试也都执行通过,这说明 Slash Command 和 Skill 的差别,并不在于谁有没有能力读代码、改文件、跑测试,这些能力本来就来自 Claude Code 本身。

更重要的是,两边虽然都完成了任务,但最终产出的风格并不完全一样。command-demo 的修复更保守一些,除了修复了安全问题,还额外做了对象类型检查,并通过返回新对象避免属性泄露,而 skill-demo 的修复更直接,主要围绕字段校验展开。这不是说 Slash Command 和 Skill 谁更聪明,而是说明模型本身仍然有非确定性,同一个任务,即使都做对了,推理路径和修复细节也未必完全一样。Skill 真正的优势,不是让模型更强,而是后面如果还要继续补规则、补约束、补输出格式,会更容易组织,也让产出的结果更加稳定。

这张表总结了这次实验里几个最值得注意的结果。

评估维度 Command Skill 说明
最终结果 5/5 测试通过 5/5 测试通过 两边都成功修复了问题
Token 消耗 14,441 15,930 Skill 为了加载更多上下文,成本略高一些
工具调用次数 10 14 Skill 读取了更多支持文件,也做了更多上下文组织
耗时 118s 129s 差距不大,但 Skill 的链路更长
上下文范围 主要读取 5 个核心文件 读取了 7+ 个文件 Skill 更容易把规则、模板和历史决策一起带进来
报告依据 以内联说明为主 可以引用独立规则文件 Skill 的输出更容易做到有据可循
可维护性 一般 更好 Skill 不会把所有内容都塞进一个主 prompt
动态性 更强 !command 语法可以注入运行时上下文
扩展性 额外的补充上下文可以作为独立文件接进 Skill,command 往往只能继续往同一个文件里塞内容

如果只看表格里的数字,Skill 在成本上并不占优势,它可能会多花一点 token、多做几次工具调用,耗时也略高一点。但它换来的,是更完整的上下文打包、更清楚的规则来源,以及更容易维护的组织方式。对简单任务来说,这种开销未必值得,可一旦任务开始涉及多份规则、历史决策、动态信息和长期复用,这点成本通常是划算的。

接下来,我们就基于这个例子的结果,继续看看 Slash Command 和 Skill 的更多差异。

Slash Command 的局限性

看到这里,一个更关键的问题其实就出来了,既然 Slash Command 也能读代码、改文件、跑测试,那它真正的局限到底在哪里呢?结合前面的实验,更准确的说法其实不是它做不到复杂任务,而是它很难把复杂任务稳定、可维护地表达出来。换句话说,它的问题不在能力边界,而在工程化表达能力。一件事越复杂,涉及的规则越多、上下文越杂、动态信息越强,这个问题就会暴露得越明显。

上下文组织能力弱

前面那个 command-demo 已经很能说明问题了。为了让一个 command 把事情做完整,我们不得不把代码文件、测试文件、安全规则、兼容约束和报告格式,一次性都写进同一个 .md 里。当然这样也能运行,但它更像是在往一个静态文本里不断塞信息,而不是在组织这些信息。任务一复杂,这个文件就会越来越像一篇超长说明书,而不像一个清晰的任务入口。

缺少模块化上下文复用能力

这会进一步带来第二个问题,Slash Command 很难把知识和规则自然地拆成可复用模块。比如安全规范是一套规则,兼容性约束是另一套规则,报告模板又是另一套结构,这些内容在真实项目里往往不会只被一个任务使用。但在 command 里,它们通常只能被复制进 prompt,或者被重新改写一遍。结果就是,多个 command 很容易各自带着一份相似但不完全一样的规则,时间一长,维护成本和内容不一致的问题就会一起出现。

无法注入运行时上下文

第三个局限来自动态信息。command 更擅长提前写好一份静态说明,但对运行时上下文的支持很弱。比如最新测试输出、当前构建状态、某次执行前的环境信息,很多时候你都无法提前写在 prompt 里。前面的 skill-demo 之所以更像真实工作流,很重要的一个原因,就是它可以在运行前把最新测试结果直接注入进来,而不是让模型自己去猜,或者依赖我们在对话里临时补充说明。

扩展性差,会退化成巨型 prompt

Slash Command 还有一个很现实的问题,就是它很难优雅地扩展。一个 command 刚开始可能只有十几行,看起来非常轻巧,但只要你不断往里面补规则、补示例、补限制、补输出格式,它很快就会膨胀成一个很长的大文件。时间一长,这种文件往往会变得很难维护,因为任何一个小改动,都可能影响整份 prompt 的整体表达。

可控性差,行为更依赖模型理解力

最后一个问题在真实使用里也很重要,那就是它对行为的控制还不够稳定。前面的实验里,两边虽然都完成了任务,但生成出来的修复方式并不完全一样,这当然不全是 command 的问题,模型本来就有非确定性。不过 command 的规则大多混在一整段长文本里,模型需要自己去理解、自己去判断优先级、自己决定哪些要求更重要,这会让很多取舍都落到模型自己判断上,所以行为也更容易波动。

Skill 是如何解决这些问题的

如果把这些问题放在一起看,Skill 的解决方式其实可以概括成一点,就是它把原本只能塞进单个 prompt 里的内容,改成了一套更清楚的结构化组织方式。主入口放在 SKILL.md,规则、模板、约束、脚本和补充材料可以分开放,运行时信息也可以在执行前再带进来。这样一来,上下文不再全都挤在一个文件里,任务越复杂,这种差别就会越明显。

也正因为这样,前面提到的那些问题,像上下文难组织、规则难复用、动态信息难接入、文件越写越大、行为不够稳定,基本都会一起得到改善。Skill 并不是让模型突然变得更强了,而是让复杂任务终于有了一种更像工程系统的组织方式,这也是为什么官方后面会逐步把重点转到 Skill 上。

不过 Skill 本身还有不少值得单独展开的地方,比如支持文件怎么组织、动态注入怎么用、自动触发和子 agent 又是怎么配合的,后面我们会专门再写一篇文章来讲 Skill,这里就不继续往下展开了。

使用场景

我们再来看看这两者各自适合什么使用场景。

场景 推荐 理由
简单的一次性指令 Slash Command 一个 .md 文件通常就够了,不需要额外引入目录结构
需要明确点名执行的固定流程 Slash Command 你可以主动触发它,什么时候执行完全由你决定
需要引用多个上下文源的工作流 Skill 上下文可以分层组织,不会把主 prompt 撑爆
需要运行时动态信息的场景 Skill 可以在执行前注入动态内容,这不是普通 command 擅长的事情
团队间共享和复用 Skill 一个目录可以整体迁移,也更适合长期维护
频繁迭代和扩展的工作流 Skill 加文件通常比不断改一个超长 prompt 更稳妥

严格来说,能用 Slash Command 的场景,用 Skill 也一样能做。当任务足够简单时,直接写一个单文件 command,成本会更低,也更省事。但如果要开始涉及多份规则、动态信息、团队复用和长期维护,Skill 通常都会是更合适的选择。

总结

本文从 Slash Command 是什么开始,介绍了它的作用域、参数机制,以及为什么它会成为 Claude Code 里很重要的一层任务入口。对于那些会反复出现的固定任务来说,Slash Command 的价值不在于多了一个 /command 的写法,而在于它能把重复出现的 prompt 固定下来,让任务表达更一致,协作也更稳定。

不过当任务开始变复杂,涉及多份规则、动态信息、长期维护和团队复用时,单文件 command 的方式就会越来越吃力。这也是为什么官方后面会逐步把重点转到 Skill 上。对日常使用来说,简单任务用 Slash Command 往往已经够了,复杂工作流则更适合交给 Skill 来组织。

参考

关注我,一起学习各种最新的 AI 和编程开发技术,欢迎交流,如果你有什么想问想说的,欢迎在评论区留言。

赞赏

Comments