用户级 vs 项目级技能

摘要

在使用 Claude Code、Cursor、GitHub Copilot 等 AI 编程助手时,很多开发者会遇到一个令人困惑的现象:明明昨天配置好的技能(Skill)或自定义指令,今天换了一个项目目录就"消失"了。这并不是工具出了 Bug,而是技能的存放位置在起作用。本文将系统讲解"用户级(User-level)"与"项目级(Project-level)“技能的区别,剖析为什么换项目后技能会"找不到”,并给出可复用的目录结构、配置示例和排障方法。读完本文,你将能够准确判断一个技能应该放在哪里,以及如何让它在正确的范围内生效。


引言

AI 编程助手的能力边界,很大程度上取决于你给它的"技能"——也就是那些告诉它"该怎么做事"的指令、模板、脚本和知识片段。这些技能不是凭空存在的,它们必须存放在文件系统的某个位置,工具在运行时才会去加载。

问题就出在这里:不同的存放位置,对应着不同的作用域(scope)。放在用户主目录下的技能,对所有项目生效;放在项目目录下的技能,只对当前项目生效。当你在 A 项目里精心配置了一套技能,切换到 B 项目时却发现"用不了",本质上是因为那套技能被放在了 A 项目的项目级目录里,而 B 项目根本不会去读取它。

这个现象在团队协作、多仓库开发、临时脚本目录等场景中尤为常见。理解作用域规则,是高效使用 AI 工具的基本功。下面我们逐层拆解。


一、什么是"技能":先统一概念

在深入作用域之前,先明确"技能"指什么。不同工具的叫法不同,但本质相似:

工具技能的表现形式典型目录/文件
Claude CodeSkills / 自定义命令.claude/skills/、.claude/commands/
CursorRules.cursor/rules/
GitHub Copilot自定义指令.github/copilot-instructions.md
通用约定项目说明AGENTS.md、CLAUDE.md

无论叫法如何,一个"技能"通常由三部分组成:

  1. 触发描述:告诉模型"什么时候用这个技能"(如 description 字段)。
  2. 执行内容:具体的指令、模板或脚本。
  3. 元数据:名称、版本、依赖等。

下面以一个典型的 Skill 目录结构为例(以 Claude Code 风格为例,其他工具思路一致):

.claude/
└── skills/
    └── code-review/
        ├── SKILL.md          # 技能主文件,包含元数据与指令
        └── scripts/
            └── lint.sh       # 技能可调用的辅助脚本

SKILL.md 的头部通常包含 YAML 元数据:

---
name: code-review
description: 当用户要求审查代码或提交 PR 前,使用此技能进行结构化代码审查。
---

# 代码审查技能

## 步骤
1. 检查是否有未处理的 TODO 注释。
2. 检查函数是否超过 50 行。
3. 运行 `scripts/lint.sh` 并汇总结果。

关键点在于:这个 .claude/ 目录放在哪里,决定了技能的作用域。这就是下一节的主题。


二、两种作用域:用户级与项目级

技能的存放位置只有两大类,对应两种作用域。

2.1 用户级(User-level)

用户级技能存放在用户主目录下,与具体项目无关。典型路径:

~/.claude/skills/          # Claude Code 用户级技能
~/.cursor/rules/           # Cursor 用户级规则
~/.config/ai-tools/        # 某些工具的 XDG 风格配置目录

特点:

  • 全局生效:在任何项目目录下启动工具,都会加载这些技能。
  • 个人偏好:适合放"我个人的编码习惯"“我常用的代码模板”“我的提交信息格式”。
  • 不随仓库分发:不会进入 Git,团队成员看不到。

2.2 项目级(Project-level)

项目级技能存放在项目根目录下,随项目一起管理。典型路径:

<project-root>/.claude/skills/     # Claude Code 项目级技能
<project-root>/.cursor/rules/      # Cursor 项目级规则
<project-root>/.github/copilot-instructions.md

特点:

  • 仅当前项目生效:离开这个目录,技能就不存在。
  • 团队共享:通常会提交到 Git,让所有协作者使用同一套规范。
  • 项目定制:适合放"本项目的架构约定"“本项目的测试命令”“本项目的目录规范”。

2.3 一张对比表

维度用户级项目级
存放位置~/.claude/ 等主目录<project>/.claude/ 等
作用范围所有项目仅当前项目
是否进 Git否通常是
团队可见否是
典型用途个人习惯、通用模板项目规范、团队约定
换项目后仍可用找不到

最后一行就是本文要解释的核心现象。


三、为什么换项目后"找不到"技能

理解了作用域,答案其实很直接:项目级技能的作用域边界就是项目根目录。工具的加载逻辑大致如下(伪代码):

def load_skills(project_root: str) -> list[Skill]:
    skills = []

    # 1. 先加载用户级技能(全局,永远加载)
    user_dir = Path.home() / ".claude" / "skills"
    if user_dir.exists():
        skills.extend(parse_skills(user_dir))

    # 2. 再加载项目级技能(只在当前项目根目录下查找)
    project_dir = Path(project_root) / ".claude" / "skills"
    if project_dir.exists():
        skills.extend(parse_skills(project_dir))

    return skills

注意第 2 步:project_dir 是基于当前工作目录(或工具识别到的项目根)计算的。当你 cd 到另一个项目,project_root 变了,project_dir 自然指向一个不存在或为空的新路径,于是项目级技能"消失"了。

常见的误判场景:

  1. 在子目录里启动工具:某些工具只向上查找有限层级,若你在很深的子目录启动,可能识别不到项目根。
  2. 复制了代码但没复制配置:git clone 时如果 .claude/ 被 .gitignore 忽略,新克隆的仓库就没有项目级技能。
  3. 多仓库/微服务:技能配在仓库 A,你在仓库 B 里工作,当然找不到。
  4. 临时目录:在 /tmp 或临时脚本目录里调试,那里没有项目级配置。

下面用一段可运行的脚本演示这个加载逻辑,方便你验证自己的环境:

#!/usr/bin/env python3
"""演示用户级 vs 项目级技能的加载差异。"""
from pathlib import Path


def discover_skills(project_root: Path) -> dict:
    """模拟工具的技能发现逻辑,返回按作用域分类的技能列表。"""
    result = {"user": [], "project": []}

    # 用户级:固定从主目录读取
    user_skills_dir = Path.home() / ".claude" / "skills"
    if user_skills_dir.is_dir():
        result["user"] = [p.name for p in user_skills_dir.iterdir() if p.is_dir()]

    # 项目级:从传入的项目根读取
    project_skills_dir = project_root / ".claude" / "skills"
    if project_skills_dir.is_dir():
        result["project"] = [p.name for p in project_skills_dir.iterdir() if p.is_dir()]

    return result


if __name__ == "__main__":
    # 场景一:在项目 A 中工作
    project_a = Path("/path/to/project-a")
    print("项目 A 加载结果:", discover_skills(project_a))

    # 场景二:切换到项目 B(项目级技能目录不存在)
    project_b = Path("/path/to/project-b")
    print("项目 B 加载结果:", discover_skills(project_b))
    # 输出中 project 为空列表 —— 这就是"找不到"的真相

运行这段脚本,你会直观看到:user 列表在两个项目里一致,而 project 列表只在配置过的项目里非空。所谓"技能消失",不过是 project 列表变空了而已。


四、如何选择:该放用户级还是项目级

选择的标准可以用一句话概括:“这条技能是不是只对这个项目有意义?”

4.1 放用户级的信号

  • 与具体代码库无关,比如"用中文回复"“提交信息用 Conventional Commits”。
  • 你个人在所有项目里都想要的习惯。
  • 涉及个人凭据、本地路径、私有脚本,不适合进仓库。

4.2 放项目级的信号

  • 描述本项目架构,比如"本仓库使用 DDD 分层,domain 层不得依赖 infrastructure"。
  • 项目专属命令,比如"测试用 make test-unit,不要用 pytest 全量跑"。
  • 团队需要统一的代码审查清单、PR 模板。
  • 需要随代码一起版本化、评审、回滚。

4.3 一个混合示例

实际项目中,两者往往并存。下面是一个推荐的项目级目录结构,配合用户级通用技能:

# 用户级(~/.claude/skills/)
~/.claude/skills/
├── commit-style/          # 个人提交信息风格,所有项目通用
└── explain-code/          # 个人偏好的代码讲解模板

# 项目级(<project>/.claude/skills/)
<project>/.claude/skills/
├── api-convention/        # 本项目 API 命名与返回格式约定
└── db-migration/          # 本项目数据库迁移流程

这样,你在任何项目里都有 commit-style 可用,而 api-convention 只在本项目生效——职责清晰,互不干扰。


五、排障实战:技能"消失"了怎么办

遇到"换项目后找不到技能",按以下清单逐项排查。

5.1 确认当前项目根

很多工具以第一个包含特定标志文件的目录为项目根(如 .git、package.json、pyproject.toml)。在子目录启动时可能识别错。用命令确认:

# 确认当前是否在 git 仓库内,以及仓库根在哪
git rev-parse --show-toplevel 2>/dev/null || echo "当前不在 git 仓库中"

如果输出为空,说明你不在任何仓库里,项目级技能自然无处加载。

5.2 确认技能目录是否存在

# 检查项目级技能目录
ls -la "$(git rev-parse --show-toplevel)/.claude/skills" 2>/dev/null \
  || echo "项目级技能目录不存在"

# 检查用户级技能目录
ls -la ~/.claude/skills 2>/dev/null \
  || echo "用户级技能目录不存在"

5.3 确认没有被 .gitignore 吞掉

如果技能需要团队共享,务必确认它没有被忽略:

# 检查 .claude 是否被忽略
git check-ignore -v .claude/skills/ 2>/dev/null \
  && echo "警告:.claude 被 .gitignore 忽略了" \
  || echo ".claude 未被忽略,可正常提交"

5.4 用一个自检脚本一次性排查

#!/usr/bin/env bash
# check-skills.sh —— 一键排查技能加载问题
set -u

echo "== 1. 项目根 =="
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo '')"
if [[ -z "$ROOT" ]]; then
  echo "  ✗ 不在 git 仓库中,项目级技能无法加载"
  ROOT="$(pwd)"
else
  echo "  ✓ 项目根: $ROOT"
fi

echo "== 2. 项目级技能 =="
if [[ -d "$ROOT/.claude/skills" ]]; then
  echo "  ✓ 存在: $ROOT/.claude/skills"
  ls "$ROOT/.claude/skills"
else
  echo "  ✗ 不存在: $ROOT/.claude/skills"
fi

echo "== 3. 用户级技能 =="
if [[ -d "$HOME/.claude/skills" ]]; then
  echo "  ✓ 存在: $HOME/.claude/skills"
  ls "$HOME/.claude/skills"
else
  echo "  ✗ 不存在: $HOME/.claude/skills"
fi

echo "== 4. 忽略规则 =="
git check-ignore -v "$ROOT/.claude/skills" 2>/dev/null \
  && echo "  ! .claude 被忽略,团队无法共享" \
  || echo "  ✓ .claude 未被忽略"

把这段脚本保存为 check-skills.sh,chmod +x 后运行,绝大多数"找不到技能"的问题都能在 10 秒内定位。

5.5 迁移决策

排查后如果发现"技能放错了位置",迁移很简单:

# 场景:把原本误放在项目里的通用技能搬到用户级
mkdir -p ~/.claude/skills
mv ./.claude/skills/commit-style ~/.claude/skills/

# 场景:把本应共享的项目技能从用户级搬回项目
mkdir -p ./.claude/skills
mv ~/.claude/skills/api-convention ./.claude/skills/

迁移后记得验证:在另一个项目目录里启动工具,确认用户级技能仍在;在本项目里确认项目级技能已加载。


六、最佳实践小结

将上面的内容沉淀为几条可执行的建议:

  1. 默认放项目级:凡是与代码库相关的规范,优先放项目级并提交到 Git,保证团队一致。
  2. 个人偏好放用户级:与项目无关的习惯放主目录,避免污染团队仓库。
  3. 命名加前缀:项目级技能可用 proj- 前缀、用户级用 my- 前缀,便于一眼区分来源。
  4. 写清触发描述:description 字段决定模型何时调用技能,写不清等于没写。
  5. 把自检脚本纳入 CI:在 CI 里跑一次目录存在性检查,避免新成员克隆后"技能缺失"。
  6. 文档化作用域:在 README 或 AGENTS.md 里说明哪些技能是全局的、哪些是项目专属的。

总结

"换项目后找不到技能"不是玄学,而是作用域规则在正常运作。本文的核心结论可以浓缩为三点:

  • 技能有作用域:用户级技能存放在主目录,全局生效;项目级技能存放在项目根目录,仅当前项目生效。
  • "找不到"的本质:工具按当前项目根去查找项目级目录,换项目后路径变了,项目级技能自然为空,而用户级技能不受影响。
  • 选择靠一句话:与项目无关放用户级,与项目相关放项目级并进 Git。

理解了这一点,你不仅能解决"技能消失"的困惑,还能主动设计出清晰的技能分层:通用的个人习惯随身携带,项目的团队规范随仓库流转。下次再遇到"技能不见了",先跑一遍第五节的自检脚本,答案往往就在目录结构里。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐