用 Claude Code Hook 实现「关键词 → Skill」自动映射,告别手动挂载

12 阅读7分钟

如何用 Claude Code Hook 把团队「提示词约定」固化为可执行的 Skill 自动映射?

本文面向有一定 Claude Code 使用经验的开发者。如果你的项目已经积累了大量自定义 Skill,并且正在为「每次都要手动挂载」感到烦恼,这篇文章或许能帮到你。

在一个业务系统项目的迭代过程中,我们沉淀了十几个领域专属的 Claude Code Skill,同时团队也形成了一套「[关键词] 触发对应能力」的提示词约定。但这套约定只存在于 Wiki 和口头传递里——Claude 本身并不知道 [模块A]/skill-a 之间有任何关系。每次切换业务域都要先手动调用 Skill,这个摩擦点虽小,但在高频迭代下积累下来,已经严重影响了节奏。本文记录了我们最终的解法。


背景:一个真实的工程痛点

随着项目迭代,团队往往会沉淀大量领域专属的 Claude Code Skill:

/skill-a    # 模块A 核心处理
/skill-b    # 模块B 核心处理
/skill-c    # 模块C 核心处理
/skill-d    # 模块D 核心处理

这些 Skill 极大提升了 AI 的专业能力,但有一个问题:它们都是手动挂载的局部 Skill,每次使用都要先 /skill-a,再描述需求

更麻烦的是,团队已经形成了一套「提示词约定」:

[模块A] 来表示这条需求需要用模块A相关能力处理。

这个约定存在于 Wiki、代码注释、甚至口头传递。但 Claude 不知道 [模块A]/skill-a 之间有任何关系。


目标

我们想要达成的效果:

用户输入:[模块A] 帮我处理这批数据
​
Claude 自动:
  1. 识别 [模块A] 标记
  2. 调用 /skill-a skill
  3. 结合原始请求处理任务

全程零手动,约定即规则。


思路拆解

解决这个问题有三个层次:

层次方案可靠性可维护性
提示词层在 CLAUDE.md 写映射表,让 Claude 自己判断⭐⭐⭐⭐⭐⭐⭐
基础设施层UserPromptSubmit Hook 拦截并注入⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
两者结合Hook 处理 + CLAUDE.md 兜底说明⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

推荐方案:Hook 处理 + CLAUDE.md 文档兜底。

原因:Hook 在基础设施层运行,不依赖 Claude 的「理解」和「记忆」,映射关系以代码形式固化,更可靠;CLAUDE.md 则作为文档,让团队成员和 AI 都能理解约定的含义。


完整实现

目录结构

项目根目录/
├── .claude/
│   ├── hooks/
│   │   └── skill-trigger.js     # Hook 脚本
│   ├── skill-mapping.json       # 关键词映射表(核心配置)
│   └── settings.json            # Hook 注册配置
└── CLAUDE.md                    # 项目规范文档(兜底说明)

Step 1:定义映射表

新建 .claude/skill-mapping.json,这是整套方案的核心配置,所有关键词和 Skill 的对应关系都在这里维护

{
  "模块A": "/skill-a",
  "模块B": "/skill-b",
  "模块C": "/skill-c",
  "模块D": "/skill-d",
  "模块E": "/skill-e",
  "模块F": "/skill-f"
}

设计原则:映射表与代码分离。新增一个业务场景,只需在这里加一行,不需要改任何逻辑代码。


Step 2:编写 Hook 脚本

新建 .claude/hooks/skill-trigger.js

#!/usr/bin/env node
​
/**
 * Claude Code UserPromptSubmit Hook
 * 功能:检测用户输入中的 [关键词] 标记,自动注入对应 Skill 的调用指令
 */
​
const fs = require('fs');
const path = require('path');
​
// 读取用户输入
const input = JSON.parse(fs.readFileSync('/dev/stdin', 'utf8'));
const prompt = input.prompt ?? '';
​
// 加载映射配置
const mappingPath = path.join(
  process.env.CLAUDE_PROJECT_DIR ?? process.cwd(),
  '.claude/skill-mapping.json'
);
​
// 配置文件不存在则透传,不影响正常使用
if (!fs.existsSync(mappingPath)) {
  process.exit(0);
}
​
const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
​
// 检测所有命中的关键词
const matchedSkills = [];
for (const [keyword, skill] of Object.entries(mapping)) {
  const pattern = new RegExp(`\[${keyword}\]`, 'g');
  if (pattern.test(prompt)) {
    matchedSkills.push({ keyword, skill });
  }
}
​
// 无命中,透传原始 prompt
if (matchedSkills.length === 0) {
  process.exit(0);
}
​
// 构建注入指令
const injectionLines = matchedSkills.map(
  ({ keyword, skill }) =>
    `- 检测到关键词 [${keyword}],请先调用 skill: "${skill.replace('/', '')}",获取该领域的专业能力后再处理请求。`
);
​
const injectedPrompt = `
【系统提示 - Skill 自动触发】
${injectionLines.join('\n')}
请按上述指引依次调用对应 skill,然后处理以下原始请求:
​
---
${prompt}
`.trim();
​
// 输出增强后的 prompt
const result = {
  hookSpecificOutput: {
    permittedToMakeChanges: true,
    updatedPrompt: injectedPrompt,
  },
};
​
process.stdout.write(JSON.stringify(result));

Step 3:注册 Hook

编辑 .claude/settings.json(项目级配置),将脚本挂载到 UserPromptSubmit 事件:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/skill-trigger.js"
          }
        ]
      }
    ]
  }
}

注意matcher 为空字符串表示拦截所有用户输入。如果你只想在特定条件下触发,可以填写正则表达式,例如 "\[.+\]" 表示只处理包含方括号标记的输入。


Step 4:CLAUDE.md 文档兜底

在项目 CLAUDE.md 中补充说明,让 AI 和团队成员都理解这套约定:

## Skill 关键词约定
​
本项目使用 `[关键词]` 格式的标记来声明所需的业务能力域。
Hook 系统会自动识别并调用对应的 Skill,无需手动挂载。
​
### 关键词映射表
​
| 关键词 | 对应 Skill | 说明 |
|--------|-----------|------|
| `[模块A]` | `/skill-a` | 模块A 核心业务处理 |
| `[模块B]` | `/skill-b` | 模块B 核心业务处理 |
| `[模块C]` | `/skill-c` | 模块C 核心业务处理 |
| `[模块D]` | `/skill-d` | 模块D 核心业务处理 |
​
### 使用示例

[模块A] 这批数据需要处理,帮我整理一下 [模块C][模块D] 这个需求涉及两个模块,帮我一起处理

> 多个关键词可以同时使用,Hook 会依次触发对应的所有 Skill。

运行效果演示

单关键词触发

# 用户输入
[模块A] 帮我处理这批数据

# Hook 处理后 Claude 看到的 prompt
【系统提示 - Skill 自动触发】
- 检测到关键词 [模块A],请先调用 skill: "skill-a",获取该领域的专业能力后再处理请求。
请按上述指引依次调用对应 skill,然后处理以下原始请求:

---
[模块A] 帮我处理这批数据

多关键词叠加触发

# 用户输入
[模块C][模块D] 这个需求涉及两个业务模块

# Hook 处理后 Claude 看到的 prompt
【系统提示 - Skill 自动触发】
- 检测到关键词 [模块C],请先调用 skill: "skill-c",获取该领域的专业能力后再处理请求。
- 检测到关键词 [模块D],请先调用 skill: "skill-d",获取该领域的专业能力后再处理请求。
请按上述指引依次调用对应 skill,然后处理以下原始请求:

---
[模块C][模块D] 这个需求涉及两个业务模块

普通输入透传

# 用户输入(无关键词标记)
帮我看一下这段代码有没有问题

# Hook 无操作,prompt 原样透传给 Claude

进阶:动态加载全局映射

如果你的 Skill 是跨项目共享的,可以将映射表放在用户级别的 Claude 配置目录,实现全局生效:

// skill-trigger.js 中修改映射文件查找逻辑
const mappingCandidates = [
  // 优先项目级配置
  path.join(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), '.claude/skill-mapping.json'),
  // 兜底用户级全局配置
  path.join(process.env.HOME, '.claude/skill-mapping.json'),
];

let mapping = {};
for (const p of mappingCandidates) {
  if (fs.existsSync(p)) {
    const partial = JSON.parse(fs.readFileSync(p, 'utf8'));
    mapping = { ...mapping, ...partial }; // 项目级覆盖全局级
    break;
  }
}

这样,全局通用的 Skill 放全局配置,项目专属的覆盖全局,层次清晰。


方案优缺点总结

优点

  • 零认知负担:开发者只需按约定写 [关键词],其他的交给基础设施
  • 配置即文档skill-mapping.json 本身就是 Skill 目录索引,提交到 git 后全团队共享
  • 基础设施层保障:不依赖 Claude 的上下文记忆,每次对话都稳定触发
  • 灵活扩展:新增映射只改 JSON,不动代码逻辑
  • 渐进增强:无关键词的普通输入完全不受影响

局限

  • 关键词冲突:如果两个 Skill 关键词相似,需要团队约定好命名规范,避免歧义
  • 执行顺序:多 Skill 同时触发时,Claude 会依次调用,存在顺序依赖时需要额外说明
  • Hook 维护成本:需要确保脚本在所有开发环境下可执行(Node.js 版本、权限等)

小结

这套方案的本质是:把隐性的团队约定,通过基础设施层的代码显式固化下来

它不是银弹,但在以下场景下会显著提升开发体验:

  • 项目已积累 5 个以上的业务领域 Skill
  • 团队已形成稳定的「关键词提示词」约定
  • 对话场景中频繁需要切换不同业务领域

如果你的团队恰好在这个阶段,不妨试试看。