# `CMDC.Plugin.Builtin.SkillGuard`
[🔗](https://github.com/tupleyun/cmdc/blob/v0.5.3/lib/cmdc/plugin/builtin/skill_guard.ex#L1)

Skill `allowed_tools` 白名单 enforcer Plugin（v0.5.3+）。

当某些 Skill 在 SKILL.md frontmatter 声明 `allowed_tools` 字段时，
本 Plugin 在 `:before_tool` hook 计算 active skills 的 allowed_tools **并集**，
阻止不在白名单内的工具调用。

## SKILL.md frontmatter 示例

    ---
    name: elixir-testing
    description: Elixir 测试最佳实践
    allowed_tools:
      - read_file
      - grep
      - shell
    ---

## 使用

    skills = CMDC.Skill.discover([{:project, "./.cmdc/skills"}])

    CMDC.create_agent(
      model: "anthropic:claude-sonnet-4-5",
      tools: [CMDC.Tool.ReadFile, CMDC.Tool.Grep, CMDC.Tool.WriteFile, CMDC.Tool.Shell],
      plugins: [
        {CMDC.Plugin.Builtin.SkillGuard,
         skills: skills,
         active: ["elixir-testing"],
         enforce_mode: :strict}
      ]
    )

## 选项

- `:skills` —— `[CMDC.Skill.t()]` Plugin 启动时全量传入（与 `Options.skills_dirs`
  自动 discover 的 Skills 一致）
- `:active` —— `[String.t()]` 当前激活的 Skill name 列表;默认全部 `:skills` 都激活
- `:enforce_mode` —— `:strict`（默认，block_tool）/ `:warn`（仅日志不 block）

## allowed_tools 并集计算规则

- **无 active skill** 或 **`active = []`** → `:unrestricted`（所有工具允许）
- **任一 active skill 的 `allowed_tools` 为 nil** → `:unrestricted`（该 skill
  显式声明不限制，并集自然不限制）
- **所有 active skills 都设了 `allowed_tools`** → 取并集，工具调用必须在并集内

并集语义（而非交集）让"用户主动激活某 skill 就放开对应工具"语义自然。

## block_tool 返回示例

    {:error, "Tool 'edit_file' not in active Skills' allowed_tools whitelist " <>
             "(active: ["elixir-testing"], allowed: ["read_file", "grep", "shell"])"}

## 优先级

`priority/0` 返回 `20`，在 `SecurityGuard`（10）之后但在大多数业务 Plugin 之前。

## 与其他 Plugin 的关系

- `SecurityGuard` 优先 → 路径 / 命令黑名单先 enforce
- `SkillGuard` 次之 → Skill 上下文白名单 enforce
- 业务 Plugin (priority >= 100) 最后

## 不影响的行为

- 工具列表本身（`Options.tools`）不改 —— Plugin 只在调用时拦截，不"卸载"工具
- 不影响 SystemPrompt 注入 —— Skill 的 description/snippet 照常注入
- 不影响 Skill 加载链路（`Skill.discover` / `Skill.load`）

# `compute_allowed_tools`

```elixir
@spec compute_allowed_tools(%{required(String.t()) =&gt; CMDC.Skill.t()}, [String.t()]) ::
  :unrestricted | [String.t()]
```

计算指定 active skills 的 `allowed_tools` 并集。

返回:
- `:unrestricted` —— 无 active skill 或 任一 active skill 未设 allowed_tools
- `[String.t()]` —— allowed_tools 并集（去重）

## 示例

    iex> skills = %{
    ...>   "a" => %Skill{name: "a", description: "", path: "/", allowed_tools: ["read", "write"]},
    ...>   "b" => %Skill{name: "b", description: "", path: "/", allowed_tools: ["grep"]}
    ...> }
    iex> CMDC.Plugin.Builtin.SkillGuard.compute_allowed_tools(skills, ["a", "b"])
    ["read", "write", "grep"]

    iex> CMDC.Plugin.Builtin.SkillGuard.compute_allowed_tools(%{}, [])
    :unrestricted

---

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