# `CMDC.Skill`
[🔗](https://github.com/tupleyun/cmdc/blob/v0.5.3/lib/cmdc/skill.ex#L1)

Skill（技能）元数据 struct + 发现与加载系统。

Skill 是通过 SKILL.md 文件定义的领域知识片段，Agent 在启动时自动发现并按需注入到系统提示词。

## SKILL.md 文件格式

    ---
    name: elixir-testing
    description: Elixir 项目测试最佳实践
    allowed_tools:           # 可选，限制 Skill 激活时 Agent 可用工具
      - read_file
      - grep
      - shell
    ---

    ## 测试规范

    - 使用 ExUnit，describe 按功能分组
    - Mock 外部服务，不 Mock 内部模块
    ...

## 两级目录

- **项目级**：`.cmdc/skills/` — 项目特定，优先级高于全局
- **全局级**：`~/.cmdc/skills/` — 所有项目通用

同名 Skill 以项目级为准（覆盖全局）。

## Skill Identity（`.skill_id` sidecar）

每个 Skill 目录可包含 `.skill_id` 文件，存储该 Skill 的持久唯一标识。
`discover/1` 首次发现时自动生成（格式 `{name}__imp_{uuid8}`），后续读取已有值。
此 ID 可供外部系统（如 cmdc_skill_engine）追踪版本、进化谱系等。

## 使用示例

    # 发现所有 Skills
    skills = CMDC.Skill.discover(["./skills", "~/.cmdc/skills"])

    # 加载完整内容
    {:ok, skill_with_content} = CMDC.Skill.load(skill)

    # 生成系统提示词注入片段（仅 name + description，不含完整内容）
    snippet = CMDC.Skill.to_prompt_snippet(skills)

# `source`

```elixir
@type source() :: :base | :user | :project | :team | :custom | nil
```

Skill 来源标签（v0.5.2+）—— 标识 Skill 来自哪一层目录。

- `:base` —— 内置 / 包默认 skills（如 `priv/skills`）
- `:user` —— 用户家目录（如 `~/.cmdc/skills`）
- `:project` —— 项目本地（如 `.cmdc/skills`）
- `:team` —— 团队共享（如 `./team_skills`）
- `:custom` —— 调用方自定义命名（兜底）
- `nil` —— 未声明来源（兼容老 `discover([dir1, dir2])` 调用）

# `t`

```elixir
@type t() :: %CMDC.Skill{
  allowed_tools: [String.t()] | nil,
  compatibility: String.t() | nil,
  content: String.t() | nil,
  description: String.t(),
  license: String.t() | nil,
  metadata: map(),
  name: String.t(),
  path: String.t(),
  skill_id: String.t() | nil,
  source: source()
}
```

# `discover`

```elixir
@spec discover([String.t()] | [{source(), String.t()}]) :: [t()]
```

在给定目录列表中发现所有 SKILL.md 文件，返回解析后的 `%Skill{}` 列表。

## 入参重载（v0.5.2 多源叠加）

两种入参形式，都按**优先级从低到高**排列（后面的覆盖前面的同名 Skill）：

1. **传统形式 `[String.t()]`** —— 纯目录路径列表（向后兼容）。
   返回的 `%Skill{}` 的 `:source` 字段为 `nil`。

       skills = CMDC.Skill.discover(["~/.cmdc/skills", "./.cmdc/skills"])

2. **多源标签形式 `[{source(), String.t()}]`** —— 元组列表，含来源标签。
   返回的 `%Skill{}` 的 `:source` 字段标识来自哪一层。

       skills = CMDC.Skill.discover([
         {:base,    "priv/skills"},
         {:user,    "~/.cmdc/skills"},
         {:project, "./.cmdc/skills"},
         {:team,    "./team_skills"}
       ])

## 同名 Skill 覆盖规则

同名 Skill 按列表顺序覆盖（后者胜出）。返回的 `%Skill{}` 携带**胜出层**
的 `:source` 字段，便于上游 Plugin / TUI 显示「这个 Skill 来自哪一层」。

## 示例

    iex> [base_dir, user_dir] = [...]
    iex> skills = CMDC.Skill.discover([{:base, base_dir}, {:user, user_dir}])
    iex> Enum.find(skills, & &1.name == "elixir-testing").source
    :user   # 用户层 override 了 base 层的同名 skill

# `find`

```elixir
@spec find([t()], String.t()) :: t() | nil
```

按名称查找 Skill，未找到返回 `nil`。

# `find_by_id`

```elixir
@spec find_by_id([t()], String.t()) :: t() | nil
```

按 skill_id 查找 Skill，未找到返回 `nil`。

# `load`

```elixir
@spec load(t()) :: {:ok, t()} | {:error, String.t()}
```

加载单个 Skill 的完整内容（包含 SKILL.md 正文部分）。

如果 Skill 已有 `:content`，直接返回；否则从文件重新读取。

## 示例

    {:ok, loaded} = CMDC.Skill.load(skill)
    loaded.content # => "## 测试规范

..."

# `to_prompt_snippet`

```elixir
@spec to_prompt_snippet([t()]) :: String.t()
```

生成系统提示词注入片段（仅 name + description 列表，不含完整内容）。

供 Agent 启动时注入到系统提示词，引导 LLM 了解可用的 Skills。
LLM 可在需要时通过工具加载完整内容。

## 示例

    snippet = CMDC.Skill.to_prompt_snippet(skills)
    # =>
    # ## 可用 Skills
    #
    # - **elixir-testing**: Elixir 项目测试最佳实践
    # - **git-workflow**: Git 工作流约定

# `write_skill_id`

```elixir
@spec write_skill_id(String.t(), String.t()) :: :ok | {:error, term()}
```

写入（或覆盖）Skill 目录下的 `.skill_id` sidecar 文件。

供外部系统（如 cmdc_skill_engine）在 FIX/DERIVED/CAPTURED 进化后
更新 Skill 身份标识。

---

*Consult [api-reference.md](api-reference.md) for complete listing*
