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) 的合并顺序是:
kotlin
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
技能目录只支持这一种结构:
objectivec
.claude/
skills/
my-skill/
SKILL.md
my-skill 会成为命令名,用户可以输入:
bash
/my-skill 参数
如果放在用户目录,则是:
javascript
~/.claude/
skills/
my-skill/
SKILL.md
3.2 旧版目录:.claude/commands
旧版命令目录仍被支持:
objectivec
.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 最小自定义技能示例
yaml
---
description: 生成当前改动的代码审查清单
argument-hint: "[重点区域]"
when_to_use: 用户希望快速审查当前分支或工作区改动时
---
请审查当前仓库的改动,重点关注:$ARGUMENTS。
输出:
1. 高风险问题
2. 可维护性问题
3. 建议补充的测试
4. 可以直接合并的理由或阻塞项
放到:
bash
.claude/skills/review-changes/SKILL.md
调用:
bash
/review-changes auth 模块
4.3 带命名参数的示例
yaml
---
description: 根据 issue 编号生成修复计划
argument-hint: "<issue> <scope>"
arguments:
- issue
- scope
---
请为 issue $issue 生成修复计划。
范围:$scope
要求:
- 先定位相关文件
- 说明风险
- 给出测试计划
命令内容最终会通过 substituteArguments(...) 替换参数。
4.4 只能由 Claude 调用的技能
yaml
---
description: 安全审计技能
when_to_use: 当任务涉及认证、权限、外部输入、密钥或命令执行时使用
user-invocable: false
---
对当前任务做安全审计,重点检查注入、权限绕过、敏感信息泄露和不安全默认值。
用户直接输入 /security-audit 时会收到提示:该技能只能由 Claude 调用。用户可以说"请使用 security-audit 技能"。
4.5 fork 子代理技能
yaml
---
description: 在独立上下文中做大型代码审查
when_to_use: 当改动跨多个模块,需要独立上下文审查时使用
context: fork
agent: code-reviewer
effort: high
---
请审查当前分支的代码改动,输出关键问题和建议。
执行时不会把技能正文简单塞进当前对话,而是走 executeForkedSlashCommand(...),调用 runAgent(...) 在子代理中执行。
4.6 带 hooks 的技能
yaml
---
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(...) 的匹配逻辑是:
scss
_.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'shouldQuerymetaMessagesnextInputsubmitNextInput
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
yaml
---
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
- 明确指出未验证项
调用:
bash
/pr-description main
7.2 只能模型调用的领域技能
文件:.claude/skills/db-review/SKILL.md
yaml
---
description: 数据库变更审查
when_to_use: 当任务涉及 SQL、migration、索引、事务或数据库性能时使用
disable-model-invocation: false
user-invocable: false
---
请审查数据库相关改动,重点检查:
- migration 是否可回滚
- 大表 DDL 是否会长时间锁表
- 查询是否有合适索引
- 是否存在 SQL 注入风险
用户不能直接 /db-review,但可以要求 Claude 使用该技能。
7.3 按路径激活的条件技能
yaml
---
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
bash
.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 执行路径
css
用户输入: /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 时:
scss
模型输出: 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.1 allowed-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),再做可用性过滤和动态技能插入。
核心链路:
scss
getCommands(cwd)
└─ loadAllCommands(cwd)
├─ getSkills(cwd)
│ ├─ getSkillDirCommands(cwd)
│ ├─ getPluginSkills()
│ ├─ getBundledSkills()
│ └─ getBuiltinPluginSkillCommands()
├─ getPluginCommands()
├─ getWorkflowCommands(cwd)
└─ COMMANDS()
getSkillDirCommands(cwd) 又会加载:
bash
managed .claude/skills
user ~/.claude/skills
project .claude/skills
additional --add-dir .claude/skills
legacy .claude/commands
11.2 Markdown 转 Command 的原理
SKILL.md 或旧版 .md 文件会经历:
scss
读取 Markdown
→ parseFrontmatter(...)
→ parseSkillFrontmatterFields(...)
→ createSkillCommand(...)
→ 返回 type: 'prompt' 的 Command
createSkillCommand(...) 生成的命令固定是 type: 'prompt'。因此 Markdown 自定义命令不是直接执行 JS 函数,而是生成一段 prompt 内容,让 Claude Code 把它作为用户消息或子代理任务处理。
11.3 执行原理
用户输入 /name args 后:
scss
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.4 user-invocable: false 是什么效果
用户不能直接输入 /skill-name 调用;如果模型可调用未禁用,Claude 仍可通过 SkillTool 使用它。
13.5 context: 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 或插件 |
16. 参考文献
| 名称 | 地址 | 说明 |
|---|---|---|
claude-code-rev |
github.com/kunge2013/c... | 本文源码分析参考的 Claude Code 恢复版仓库 |
| Hooks 文档 | ./claude-code-hooks.md |
本文关联的 hooks 生命周期与配置说明 |