Claude Code 工作流中的命令实现与自定义指南
本文基于 claude-code-rev 源码分析 Claude Code 工作流中的命令系统:命令从哪里加载、如何被识别、如何执行、能否自定义、如何编写自定义命令/技能,以及这些命令与模型、工具权限、插件、工作流和 hooks 的关系。
结论先行:Claude Code 的命令可以自定义。最推荐的方式是写 Markdown 形式的命令或技能;更深度的交互式命令需要插件或源码级 local / local-jsx 实现;工作流命令和 MCP skill 也能进入命令系统,但它们依赖对应特性或外部服务。
0. 整体流程图
命令工作流可以拆成四层:
- 命令来源:内置命令、.claude/skills、旧版 .claude/commands、插件、工作流;MCP skills 走独立的 MCP 命令集合。
- 加载过滤:loadAllCommands(cwd) 合并本地/插件/工作流来源,getCommands(cwd) 按可用性、开关和动态技能过滤。
- 用户输入解析:用户输入 /command args 后,processSlashCommand(...) 解析命令名和参数,并从当前命令表中查找命令。
- 执行分支:根据 Command.type 进入 prompt、local、local-jsx,其中 prompt 命令还可以通过 context: fork 进入子代理执行;MCP skills 主要通过单独筛选后进入模型可调用技能集合。

1. 命令的核心类型
源码中的命令统一抽象为 Command,定义在 src/types/command.ts。它由公共字段 CommandBase 加上三种执行形态之一组成。
| 命令类型 |
源码类型 |
主要用途 |
是否适合用户自定义 |
执行结果 |
| prompt |
PromptCommand |
把 Markdown/文本内容扩展成模型输入;可作为技能被用户或 Claude 调用 |
适合,最推荐 |
生成用户消息,通常继续让模型处理;也可 context: fork 由子代理执行 |
| local |
LocalCommand |
执行本地 TypeScript/JS 模块逻辑,不渲染交互 UI |
不适合普通配置,需要源码或插件代码 |
返回 text、compact 或 skip |
| local-jsx |
LocalJSXCommand |
渲染 Ink/React 交互界面,例如选择器、配置面板 |
不适合普通配置,需要源码或插件代码 |
通过 onDone(...) 回传结果、是否继续查询、下一条输入等 |
关键字段:
| 字段 |
出现位置 |
含义 |
| name |
CommandBase |
命令内部名称,用户通常通过 /<name> 调用 |
| aliases |
CommandBase |
命令别名,findCommand(...) 会匹配别名 |
| description |
CommandBase |
命令说明,显示在补全、帮助或 SkillTool 中 |
| argumentHint |
CommandBase |
参数提示,来自 frontmatter 的 argument-hint |
| whenToUse |
CommandBase |
技能适用场景,来自 frontmatter 的 when_to_use |
| userInvocable |
CommandBase |
是否允许用户直接输入 /name 调用;false 时只能让 Claude 通过 Skill 工具调用 |
| disableModelInvocation |
CommandBase |
是否禁止模型通过 SkillTool 调用 |
| loadedFrom |
CommandBase |
命令来源,例如 skills、commands_DEPRECATED、plugin、bundled、mcp |
| kind |
CommandBase |
当前主要用于标记 workflow |
| isSensitive |
CommandBase |
对敏感参数进行历史记录脱敏 |
| allowedTools |
PromptCommand |
Markdown 命令中内联 shell 片段可用的工具 allowlist |
| model |
PromptCommand |
命令或技能指定的模型 |
| hooks |
PromptCommand |
技能被调用时临时注册的 hooks |
| context |
PromptCommand |
inline 或 fork;fork 会使用子代理单独执行 |
| agent |
PromptCommand |
context: fork 时指定子代理类型 |
| effort |
PromptCommand |
子代理或模型推理 effort |
| paths |
PromptCommand |
条件技能匹配文件路径后才激活 |
2. 命令从哪里来
src/commands.ts 是命令聚合入口。内置命令由 COMMANDS() 返回,技能、插件和工作流再由 loadAllCommands(cwd) 合并。
2.1 来源总表
| 来源 |
典型目录/入口 |
加载函数 |
loadedFrom / source |
说明 |
| 内置命令 |
src/commands/* |
COMMANDS() |
source: builtin |
例如 /help、/clear、/model、/hooks、/status 等 |
| 用户/项目技能 |
.claude/skills/<skill>/SKILL.md、~/.claude/skills/<skill>/SKILL.md |
getSkillDirCommands(cwd) |
loadedFrom: skills |
推荐的自定义方式,目录格式固定为 skill-name/SKILL.md |
| 旧版自定义命令 |
.claude/commands/*.md、.claude/commands/**/SKILL.md |
loadSkillsFromCommandsDir(cwd) |
loadedFrom: commands_DEPRECATED |
仍支持,适合兼容旧用法;新内容建议迁移到 skills |
| bundled skills |
构建内置的技能包 |
getBundledSkills() |
loadedFrom: bundled |
随 Claude Code 打包分发 |
| built-in plugin skills |
内置插件提供 |
getBuiltinPluginSkillCommands() |
取决于插件 |
来自启用的内置插件 |
| plugin commands |
插件命令 |
getPluginCommands() |
loadedFrom: plugin |
插件可提供命令能力 |
| plugin skills |
插件技能 |
getPluginSkills() |
loadedFrom: plugin |
插件提供的 prompt 型技能 |
| workflow commands |
工作流脚本 |
getWorkflowCommands(cwd) |
kind: workflow |
受 WORKFLOW_SCRIPTS feature gate 控制 |
| MCP skills |
MCP 服务暴露 |
getMcpSkillCommands(...) |
loadedFrom: mcp |
不经过 loadAllCommands(cwd);从 AppState.mcp.commands 单独筛选后作为模型可调用 skill 进入系统 |
| dynamic skills |
运行时发现 |
getDynamicSkills() |
通常仍是 prompt command |
由文件触达或动态发现机制插入 |
2.2 合并顺序
loadAllCommands(cwd) 的合并顺序是:
|
1
2
3
4
5
6
7
8
9
|
return [
...bundledSkills,
...builtinPluginSkills,
...skillDirCommands,
...workflowCommands,
...pluginCommands,
...pluginSkills,
...COMMANDS(),
]
|
这意味着命令列表中,技能和插件命令会出现在内置命令之前。运行时查找由 findCommand(...) 按数组顺序返回第一个匹配项,因此同名命令可能受到加载顺序影响。实践中建议自定义命令避免与内置命令重名。
2.3 过滤逻辑
getCommands(cwd) 会在合并后做两类过滤:
| 过滤点 |
源码函数 |
作用 |
| 可用性过滤 |
meetsAvailabilityRequirement(cmd) |
根据 availability 限制命令只对某类认证/服务提供方可见 |
| 开关过滤 |
isCommandEnabled(cmd) |
根据 feature flag、环境变量或运行时状态决定命令是否启用 |
| 动态技能插入 |
getDynamicSkills() |
把运行时发现的技能插到插件技能之后、内置命令之前 |
3. Markdown 命令和技能如何加载
自定义命令最重要的入口在 src/skills/loadSkillsDir.ts 和 src/utils/markdownConfigLoader.ts。
3.1 推荐目录:.claude/skills
技能目录只支持这一种结构:
|
1
2
3
4
|
.claude/
skills/
my-skill/
SKILL.md
|
my-skill 会成为命令名,用户可以输入:
如果放在用户目录,则是:
|
1
2
3
4
|
~/.claude/
skills/
my-skill/
SKILL.md
|
3.2 旧版目录:.claude/commands
旧版命令目录仍被支持:
|
1
2
3
4
5
6
7
|
.claude/
commands/
review.md
git/
pr.md
deploy/
SKILL.md
|
命名规则:
| 文件形态 |
命令名 |
| .claude/commands/review.md |
/review |
| .claude/commands/git/pr.md |
/git:pr |
| .claude/commands/deploy/SKILL.md |
/deploy |
| .claude/commands/team/release/SKILL.md |
/team:release |
源码中 buildNamespace(...) 会把子目录转换成冒号命名空间。
3.3 加载范围和优先级
loadMarkdownFilesForSubdir(...) 会加载三类位置:
| 位置 |
路径 |
说明 |
| managed/policy |
managed path 下的 .claude/<subdir> |
组织策略或托管配置,优先级最高 |
| user |
~/.claude/<subdir> |
用户全局命令/技能 |
| project |
从当前目录向上查找 .claude/<subdir>,直到 git root 或 home |
项目级命令/技能 |
Markdown 文件加载组合顺序是 managed > user > project。文件层面会按真实文件 identity 去重,技能层面也会按 realpath 去重,顺序靠前者保留。
项目目录向上查找会在 git root 停止,避免父目录的 .claude/commands 或 .claude/skills 意外泄露进无关仓库。worktree 场景下,如果 worktree 缺少 .claude/<subdir>,会回退到主仓库对应目录。
4. Frontmatter 字段参考
Markdown 命令/技能通过 YAML frontmatter 定义元数据。字段解析主要在 src/utils/frontmatterParser.ts 和 src/skills/loadSkillsDir.ts。
4.1 常用字段
| 字段 |
类型 |
用于 |
含义 |
| description |
string |
命令/技能 |
显示说明;缺失时会从正文第一行提取 |
| argument-hint |
string |
命令/技能 |
参数提示,例如 [branch]、<issue-id> |
| arguments |
string 或 string[] |
命令/技能 |
声明命名参数,供正文替换使用 |
| allowed-tools |
string 或 string[] |
prompt 命令 |
允许内联 shell 片段使用的工具;缺失默认为空 |
| when_to_use |
string |
skill |
给模型看的“什么时候使用”说明 |
| version |
string |
skill |
技能版本 |
| model |
string |
prompt 命令 |
指定模型;inherit 表示继承父上下文 |
| user-invocable |
boolean-like string |
skill |
是否允许用户直接 /name 调用;默认 true |
| disable-model-invocation |
boolean-like string |
skill |
是否禁止模型通过 SkillTool 调用 |
| hooks |
HooksSettings |
skill |
技能被调用时注册临时 hooks |
| context |
inline 或 fork |
skill |
fork 会启动子代理执行 |
| agent |
string |
fork skill |
指定子代理类型 |
| effort |
string 或 integer |
fork skill |
指定推理 effort |
| paths |
string 或 string[] |
skill |
条件技能,匹配文件路径后才激活 |
| shell |
bash 或 powershell |
skill/command |
控制 Markdown 内 ! shell 片段使用的 shell |
4.2 最小自定义技能示例
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
---
description: 生成当前改动的代码审查清单
argument-hint: "[重点区域]"
when_to_use: 用户希望快速审查当前分支或工作区改动时
---
?
请审查当前仓库的改动,重点关注:$ARGUMENTS。
?
输出:
1. 高风险问题
2. 可维护性问题
3. 建议补充的测试
4. 可以直接合并的理由或阻塞项
|
放到:
|
1
|
.claude/skills/review-changes/SKILL.md
|
调用:
|
1
|
/review-changes auth 模块
|
4.3 带命名参数的示例
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
---
description: 根据 issue 编号生成修复计划
argument-hint: "<issue> <scope>"
arguments:
- issue
- scope
---
请为 issue $issue 生成修复计划。
范围:$scope
要求:
- 先定位相关文件
- 说明风险
- 给出测试计划
|
命令内容最终会通过 substituteArguments(...) 替换参数。
4.4 只能由 Claude 调用的技能
|
1
2
3
4
5
6
|
---
description: 安全审计技能
when_to_use: 当任务涉及认证、权限、外部输入、密钥或命令执行时使用
user-invocable: false
---
对当前任务做安全审计,重点检查注入、权限绕过、敏感信息泄露和不安全默认值。
|
用户直接输入 /security-audit 时会收到提示:该技能只能由 Claude 调用。用户可以说“请使用 security-audit 技能”。
4.5 fork 子代理技能
|
1
2
3
4
5
6
7
8
|
---
description: 在独立上下文中做大型代码审查
when_to_use: 当改动跨多个模块,需要独立上下文审查时使用
context: fork
agent: code-reviewer
effort: high
---
请审查当前分支的代码改动,输出关键问题和建议。
|
执行时不会把技能正文简单塞进当前对话,而是走 executeForkedSlashCommand(...),调用 runAgent(...) 在子代理中执行。
4.6 带 hooks 的技能
|
1
2
3
4
5
6
7
8
9
10
11
|
---
description: 编辑 TypeScript 后自动检查
hooks:
PostToolUse:
- matcher: "Write|Edit|MultiEdit"
hooks:
- type: command
command: "npm run typecheck"
timeout: 60
---
请实现用户请求,并在修改 TypeScript 文件后自动触发类型检查。
|
源码中 getMessagesForPromptSlashCommand(...) 会在命令带有 hooks 时调用 registerSkillHooks(...),这些 hook 以会话级方式注册。
5. 用户输入/command后发生什么
src/utils/processUserInput/processSlashCommand.tsx 负责 slash command 的运行时处理。
5.1 解析与查找
流程如下:
| 步骤 |
源码函数 |
行为 |
| 解析输入 |
parseSlashCommand(inputString) |
拆出 commandName、args、isMcp |
| 判断存在 |
hasCommand(commandName, context.options.commands) |
不存在时判断是否像文件路径;否则返回 Unknown skill |
| 获取命令 |
getCommand(commandName, context.options.commands) |
按 name、userFacingName()、aliases 查找 |
| 检查调用权限 |
command.userInvocable === false |
禁止用户直接调用,只允许 Claude 使用 SkillTool |
| 分支执行 |
switch (command.type) |
进入 local-jsx、local 或 prompt 分支 |
findCommand(...) 的匹配逻辑是:
|
1
2
3
|
_.name === commandName ||
getCommandName(_) === commandName ||
_.aliases?.includes(commandName)
|
5.2 prompt命令执行
普通 prompt 命令会:
- 调用 command.getPromptForCommand(args, context) 生成内容块。
- 注册技能 hooks(如果 frontmatter 中定义了 hooks)。
- 解析 allowedTools,把额外工具权限传给后续模型处理。
- 创建用户消息,通常设置 shouldQuery: true,让 Claude 接着处理。
Markdown 技能的 getPromptForCommand(...) 还会做这些替换/处理:
| 处理 |
说明 |
| 添加 base directory |
有 baseDir 时,正文前会加 Base directory for this skill: ... |
| 参数替换 |
通过 $ARGUMENTS 或命名参数替换用户输入 |
| ${CLAUDE_SKILL_DIR} |
替换成技能目录,便于引用技能自带脚本 |
| ${CLAUDE_SESSION_ID} |
替换成当前 session id |
| 执行 Markdown 内 shell 片段 |
非 MCP 技能可以执行 ! shell 片段;MCP 技能禁止执行 |
5.3 context: fork的执行
当 PromptCommand.context === 'fork' 时,流程进入 executeForkedSlashCommand(...):
| 阶段 |
行为 |
| 准备上下文 |
prepareForkedCommandContext(...) 生成技能内容、agent 定义、prompt messages |
| 创建 agent id |
createAgentId() |
| 启动子代理 |
runAgent(...) 使用指定 agent、model、effort 和可用工具 |
| 收集结果 |
extractResultText(...) 提取子代理最终输出 |
| 返回结果 |
结果包装成 <local-command-stdout>,不再直接让主模型查询 |
在 KAIROS/assistant 模式下,fork 命令还可能以后台方式运行,完成后把结果重新放回消息队列。
5.4 local命令执行
local 命令是源码或插件中的 JS/TS 模块。执行逻辑:
先构造用户输入消息。
await command.load() 懒加载模块。
调用 mod.call(args, context)。
根据返回结果处理:
- { type: 'skip' }:不写消息,不继续查询。
- { type: 'text', value }:输出 <local-command-stdout>。
- { type: 'compact', compactionResult }:进入上下文压缩后的消息构造流程。
5.5 local-jsx命令执行
local-jsx 命令适合交互 UI,例如选择器、设置面板。执行逻辑:
command.load() 懒加载 JSX 模块。
调用 mod.call(onDone, context, args)。
返回 React 节点后通过 setToolJSX(...) 渲染。
UI 完成后调用 onDone(result, options)。
options 可控制:
- display: 'skip' | 'system' | 'user'
- shouldQuery
- metaMessages
- nextInput
- submitNextInput
6. 命令是否可以自定义
可以,但不同层级能力不同。
| 自定义方式 |
是否需要写代码 |
能力 |
适用场景 |
推荐程度 |
| .claude/skills/<name>/SKILL.md |
否 |
prompt 命令、模型技能、可 fork、可带 hooks、可指定模型和工具 |
项目/个人常用流程、审查、生成、分析、规范化任务 |
最高 |
| .claude/commands/*.md |
否 |
旧版 prompt 命令,支持命名空间 |
兼容旧项目或快速添加 /foo |
中等,建议新建用 skills |
| 插件命令/技能 |
是 |
可分发、可封装复杂能力 |
团队/生态共享命令 |
高,但复杂度更高 |
| 工作流命令 |
取决于工作流 |
标记为 workflow 的命令 |
可复用自动化工作流 |
取决于 feature 是否启用 |
| MCP skills |
外部 MCP 服务 |
远端服务暴露技能 |
外部系统集成 |
适合系统集成 |
| 修改 src/commands/* |
是 |
完整 local/local-jsx/prompt 能力 |
fork 本项目或实现内置级交互命令 |
仅适合维护者 |
7. 自定义命令实战模板
7.1 项目级命令:生成 PR 描述
文件:.claude/skills/pr-description/SKILL.md
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
---
description: 根据当前分支生成 PR 描述
argument-hint: "[目标分支]"
when_to_use: 用户准备创建 pull request 或需要总结当前分支改动时
allowed-tools:
- Bash(git status:*)
- Bash(git diff:*)
- Bash(git log:*)
---
请基于当前分支相对 $ARGUMENTS 的改动生成 PR 描述。
要求:
- 标题不超过 70 字符
- Summary 使用 1-3 个 bullet
- Test plan 使用 checklist
- 明确指出未验证项
|
调用:
7.2 只能模型调用的领域技能
文件:.claude/skills/db-review/SKILL.md
|
1
2
3
4
5
6
7
8
9
10
11
12
|
---
description: 数据库变更审查
when_to_use: 当任务涉及 SQL、migration、索引、事务或数据库性能时使用
disable-model-invocation: false
user-invocable: false
---
请审查数据库相关改动,重点检查:
- migration 是否可回滚
- 大表 DDL 是否会长时间锁表
- 查询是否有合适索引
- 是否存在 SQL 注入风险
|
用户不能直接 /db-review,但可以要求 Claude 使用该技能。
7.3 按路径激活的条件技能
|
1
2
3
4
5
6
7
8
9
|
---
description: React 组件审查
when_to_use: 当修改 React/TSX 文件时检查组件结构和状态管理
paths:
- "src/**/*.tsx"
- "app/**/*.tsx"
---
当用户修改 React 组件时,检查 props 类型、状态管理、副作用和可访问性。
|
带 paths 的技能会先进入 conditional skill 存储,只有匹配路径被触达后才激活。
8. 命令和 SkillTool 的关系
Claude Code 把很多 prompt 型命令也暴露给模型作为 SkillTool 可调用能力。
| 函数 |
作用 |
| getSkillToolCommands(cwd) |
返回模型可调用的 prompt commands,包括 .claude/skills、bundled skills 和旧版 .claude/commands |
| getSlashCommandToolSkills(cwd) |
更偏向“技能”列表,要求有 description 或 whenToUse,并来自 skills/plugin/bundled 等来源 |
| getMcpSkillCommands(mcpCommands) |
从 MCP 命令中筛出 prompt 型、模型可调用、loadedFrom: mcp 的技能 |
影响模型是否可调用的关键字段:
| 字段 |
效果 |
| disable-model-invocation: true |
不进入模型可调用技能列表 |
| user-invocable: false |
用户不能直接 /name 调用,但模型仍可调用,除非同时禁用模型调用 |
| description / when_to_use |
对插件/MCP 类技能尤其重要,决定是否展示给模型或工具列表 |
8.1 Skill 变成命令的完整流程
从 Markdown skill 文件到可执行命令,经历以下阶段:
8.1.1 加载阶段:Markdown → PromptCommand
|
1
2
3
4
5
6
7
8
9
10
11
|
.claude/skills/example.md
↓ loadSkillsDir.ts 读取文件
↓ parseMarkdown() 解析 frontmatter + body
↓ 构建 PromptCommand 对象
{
type: 'prompt',
path: '/path/to/example.md',
prompt: 'body 内容',
allowedTools: frontmatter.allowed-tools,
...
}
|
源码位置:src/skills/loadSkillsDir.ts 的 getPromptForCommand() (344-405行)
8.1.2 用户输入/skill-name执行路径
|
1
2
3
4
5
6
|
用户输入: /example arg1 arg2
↓ processSlashCommand.tsx 检测 "/" 开头
↓ findMatchingCommand() 查找匹配的命令
↓ getMessagesForPromptSlashCommand() 生成消息
↓ getPromptForCommand() 处理 prompt 内容
↓ 返回 { messages, allowedTools, hooks }
|
关键函数:src/utils/processUserInput/processSlashCommand.tsx 第827行
8.1.3 getPromptForCommand() 详细处理步骤
| 步骤 |
操作 |
源码位置 |
| 1 |
添加 base dir prefix |
第352行 |
| 2 |
替换 $ARGUMENTS |
第360-375行 |
| 3 |
替换 ${CLAUDE_SKILL_DIR} |
第380行 |
| 4 |
替换 ${CLAUDE_SESSION_ID} |
第385行 |
| 5 |
执行内联 ! shell 片段 |
第390-405行 |
8.1.4 SkillTool 调用路径
模型通过 SkillTool 调用 skill 时:
|
1
2
3
4
5
|
模型输出: Skill({ skill: "example", args: "arg1" })
↓ SkillTool.ts call() 函数 (580-841行)
↓ 判断 context: inline 还是 fork
↓ inline: 直接返回 prompt 消息
↓ fork: 启动子 Agent 执行
|
8.1.5 Inline vs Fork 执行流程对比
| 类型 |
流程 |
适用场景 |
| inline |
模型收到 skill prompt → 在当前会话继续 |
简单指令、快速任务 |
| fork |
启动子 Agent → 子 Agent 执行 → 返回结果 |
复杂任务、隔离执行 |
8.1.6 关键源码位置表
| 功能 |
文件 |
行号 |
| Skill 加载 |
src/skills/loadSkillsDir.ts |
344-405 |
| Slash 命令解析 |
src/utils/processUserInput/processSlashCommand.tsx |
827 |
| SkillTool 调用 |
src/tools/SkillTool/SkillTool.ts |
580-841 |
| SkillTool 系统提示 |
src/tools/SkillTool/prompt.ts |
173-195 |
| Skills 列表注入 |
src/messages/prompt/formatPrompt.ts |
formatCommandsWithinBudget |
9. 命令和权限的关系
命令系统本身不等同于工具权限系统,但它会影响后续模型或内联 shell 的权限边界。
9.1allowed-tools
Markdown 命令 frontmatter 的 allowed-tools 会被解析为工具列表。对于 prompt 命令:
- 会传到 slash command 结果的 allowedTools。
- 在 Markdown 内联 shell 执行时,会被写入 alwaysAllowRules.command,让该命令内部的 shell 片段按命令级 allowlist 执行。
9.2 MCP 技能的限制
MCP skills 被视为远端/不可信来源,源码中明确禁止执行其 Markdown 正文里的内联 shell 片段。也就是说:
| 来源 |
是否执行 Markdown 内 ! shell 片段 |
| 本地 skills / commands |
可以,受权限与 allowed-tools 影响 |
| bundled / plugin skills |
取决于加载来源与策略 |
| MCP skills |
不执行 |
9.3 Remote / Bridge 模式限制
src/commands.ts 中还有远程模式限制:
| 限制 |
源码对象/函数 |
含义 |
| REMOTE_SAFE_COMMANDS |
remote mode 预过滤 |
只保留不依赖本地文件系统、git、shell、IDE、MCP 的命令 |
| BRIDGE_SAFE_COMMANDS |
bridge inbound allowlist |
手机/web 远程控制进入的 local 命令默认阻止,只有 allowlist 内可执行 |
| isBridgeSafeCommand(cmd) |
bridge 判断 |
prompt 命令默认安全,local-jsx 默认阻止,local 需要 allowlist |
10. 与 hooks 的关系
命令和 hooks 是两套机制,但可以互相连接:
| 连接点 |
说明 |
| 技能 frontmatter 中声明 hooks |
技能被调用后,通过 registerSkillHooks(...) 注册会话级 hooks |
| 命令触发工具调用 |
prompt 命令本身会变成模型输入,模型后续使用工具时仍会触发 hooks |
| allowed-tools 与 hooks 双重约束 |
命令可限定工具列表,hooks 可在工具真正执行前后审计或阻断 |
| fork 技能与 Subagent hooks |
context: fork 会启动子代理,相关生命周期可能触发子代理 hooks |
建议:
| 目标 |
用命令还是 hook |
| 让用户主动触发某套流程 |
用命令/技能 |
| 在工具执行前后自动校验 |
用 hook |
| 给某个技能附带临时质量门禁 |
在技能 frontmatter 中声明 hooks |
| 对所有项目统一拦截危险行为 |
用全局或项目级 hook |
11. 源码级原理说明
11.1 加载原理
getCommands(cwd) 是命令表入口。它先调用 memoized 的 loadAllCommands(cwd),再做可用性过滤和动态技能插入。
核心链路:
|
1
2
3
4
5
6
7
8
9
10
|
getCommands(cwd)
└─ loadAllCommands(cwd)
├─ getSkills(cwd)
│ ├─ getSkillDirCommands(cwd)
│ ├─ getPluginSkills()
│ ├─ getBundledSkills()
│ └─ getBuiltinPluginSkillCommands()
├─ getPluginCommands()
├─ getWorkflowCommands(cwd)
└─ COMMANDS()
|
getSkillDirCommands(cwd) 又会加载:
|
1
2
3
4
5
|
managed .claude/skills
user ~/.claude/skills
project .claude/skills
additional --add-dir .claude/skills
legacy .claude/commands
|
11.2 Markdown 转 Command 的原理
SKILL.md 或旧版 .md 文件会经历:
|
1
2
3
4
5
|
读取 Markdown
→ parseFrontmatter(...)
→ parseSkillFrontmatterFields(...)
→ createSkillCommand(...)
→ 返回 type: 'prompt' 的 Command
|
createSkillCommand(...) 生成的命令固定是 type: 'prompt'。因此 Markdown 自定义命令不是直接执行 JS 函数,而是生成一段 prompt 内容,让 Claude Code 把它作为用户消息或子代理任务处理。
11.3 执行原理
用户输入 /name args 后:
|
1
2
3
4
5
6
7
8
9
10
11
|
processSlashCommand(...)
→ parseSlashCommand(...)
→ hasCommand(...)
→ getMessagesForSlashCommand(...)
→ getCommand(...)
→ switch command.type
├─ local-jsx: load().call(onDone, context, args)
├─ local: load().call(args, context)
└─ prompt:
├─ context === 'fork' → executeForkedSlashCommand(...)
└─ inline → getMessagesForPromptSlashCommand(...)
|
11.4 为什么 Markdown 命令不能直接做任意 UI
Markdown 命令最终被转成 PromptCommand。它只有 getPromptForCommand(...),不能直接返回 React 节点,也不能直接实现 onDone 交互流程。需要交互 UI 的命令必须是 local-jsx,也就是源码或插件代码提供的命令。
11.5 为什么自定义命令推荐写成 skills
skills 目录是当前更完整的能力模型:
- 支持 SKILL.md 目录结构和技能资源文件。
- 支持 ${CLAUDE_SKILL_DIR} 引用技能目录。
- 支持 paths 条件激活。
- 支持 when_to_use 给模型选择技能。
- 支持 context: fork 启动子代理。
- 支持技能级 hooks。
旧版 .claude/commands 仍能用,但源码中标记为 commands_DEPRECATED,适合兼容,不建议作为新设计的首选。
12. 使用建议与最佳实践
| 场景 |
推荐做法 |
原因 |
| 团队共享常用流程 |
提交 .claude/skills/<name>/SKILL.md 到仓库 |
可版本化、可审查、随项目走 |
| 个人全局命令 |
放在 ~/.claude/skills/<name>/SKILL.md |
不污染项目仓库 |
| 旧项目已有 /commands |
可以继续用,但新命令迁移到 /skills |
commands_DEPRECATED 仍支持但不是首选 |
| 需要交互选择器 |
写插件或源码 local-jsx 命令 |
Markdown 无法渲染 UI |
| 需要执行本地脚本 |
优先用技能正文指导 Claude 调工具,必要时用 Markdown ! 片段并设置 allowed-tools |
保持权限边界清晰 |
| 涉及危险操作 |
不要只靠命令说明,配合 PreToolUse / PermissionRequest hooks |
命令是触发流程,hook 才是强约束点 |
| 需要模型自动选择 |
写清 description 和 when_to_use |
模型依赖这些字段判断是否使用技能 |
| 需要隔离上下文 |
使用 context: fork |
大任务不会污染主上下文,且有独立 token 预算 |
13. 常见问题
13.1/commands和/skills有什么区别
.claude/commands 是旧版 Markdown 命令目录;.claude/skills 是更完整的技能目录。两者最终都会被转成 type: 'prompt' 的 Command,但 skills 支持更明确的目录结构、资源引用、条件激活和模型技能语义。
13.2 自定义命令能覆盖内置命令吗
命令合并时自定义技能在内置命令之前,查找时返回第一个匹配项,因此同名有可能影响解析结果。但不建议依赖覆盖行为,最好避免与内置命令重名。
13.3 Markdown 命令能直接执行 shell 吗
可以使用 Markdown 内联 shell 片段,但要受权限和 allowed-tools 影响。MCP skills 不会执行内联 shell。涉及危险操作时,建议用 hooks 做强制约束。
13.4user-invocable: false是什么效果
用户不能直接输入 /skill-name 调用;如果模型可调用未禁用,Claude 仍可通过 SkillTool 使用它。
13.5context: fork和普通命令区别是什么
普通 prompt 命令会把内容加入当前对话;context: fork 会启动子代理在独立上下文中执行,最终把结果返回给主流程。
13.6 命令会自动出现在模型可用技能里吗
不一定。模型可调用技能通常要求是 prompt 类型、不是 builtin、没有 disableModelInvocation,并且有合适的 description 或 when_to_use。插件/MCP 类技能对显式描述要求更高。
14. 关键源码位置
| 文件 |
关键函数/类型 |
说明 |
| src/types/command.ts |
Command, PromptCommand, LocalCommand, LocalJSXCommand |
命令类型系统 |
| src/commands.ts |
COMMANDS() |
内置命令列表 |
| src/commands.ts |
loadAllCommands(cwd) |
合并 bundled skills、plugin skills、skill dir commands、workflow commands、plugin commands、内置命令 |
| src/commands.ts |
getCommands(cwd) |
过滤可用命令并插入动态技能 |
| src/commands.ts |
findCommand(...), getCommand(...) |
命令查找逻辑,支持别名和 user-facing name |
| src/commands.ts |
getSkillToolCommands(...), getSlashCommandToolSkills(...) |
生成模型可调用技能列表 |
| src/commands.ts |
REMOTE_SAFE_COMMANDS, isBridgeSafeCommand(...) |
远程/bridge 模式命令安全过滤 |
| src/utils/processUserInput/processSlashCommand.tsx |
processSlashCommand(...) |
用户 /command 输入解析入口 |
| src/utils/processUserInput/processSlashCommand.tsx |
getMessagesForSlashCommand(...) |
按 command.type 分支执行 |
| src/utils/processUserInput/processSlashCommand.tsx |
executeForkedSlashCommand(...) |
context: fork 子代理执行逻辑 |
| src/skills/loadSkillsDir.ts |
getSkillDirCommands(cwd) |
加载 .claude/skills 和旧版 .claude/commands |
| src/skills/loadSkillsDir.ts |
createSkillCommand(...) |
把 Markdown 技能转换成 PromptCommand |
| src/skills/loadSkillsDir.ts |
parseSkillFrontmatterFields(...) |
解析技能 frontmatter 字段 |
| src/utils/markdownConfigLoader.ts |
loadMarkdownFilesForSubdir(...) |
加载 managed/user/project Markdown 配置文件 |
| src/utils/markdownConfigLoader.ts |
getProjectDirsUpToHome(...) |
从 cwd 向上查找 .claude/<subdir>,到 git root 停止 |
| src/utils/frontmatterParser.ts |
FrontmatterData, parseFrontmatter(...) |
frontmatter 字段定义和 YAML 解析 |
15. 快速决策表
| 你想做什么 |
应该用什么 |
| 增加一个 /review,让 Claude 按固定步骤审查代码 |
.claude/skills/review/SKILL.md |
| 增加一个 /git:pr 命名空间命令 |
.claude/commands/git/pr.md 或迁移为 .claude/skills/git-pr/SKILL.md |
| 让 Claude 在修改 TS 文件后自动 typecheck |
技能 frontmatter hooks,或项目级 PostToolUse hook |
| 做一个交互式模型选择面板 |
local-jsx 命令,需要源码或插件 |
| 做一个纯本地状态查询命令 |
local 命令,需要源码或插件 |
| 让某技能只在 React 文件被修改后出现 |
paths: ["**/*.tsx"] 条件技能 |
| 把外部系统能力暴露给 Claude |
MCP skill 或插件 skill |
| 团队统一分发命令 |
项目 .claude/skills 或插件 |