<!-- title: 造环境变量Skill：扫代码秒出env模板 -->

## 概述

本教程带你从零开发一个生产级 Claude Code 自定义 Skill——**env-scan**，它能扫描任意项目代码、自动提取所有环境变量引用，并生成 `.env.example` 模板与变量清单。通过这个真实案例，你将掌握 Skill 的渐进式披露（Progressive Disclosure）机制、description 触发工程、支持文件分层、动态上下文注入与 frontmatter 精准控场。**适用人群**：已跑通 Claude Code 基础用法、想把重复提示词沉淀为可复用能力的中级开发者。

## 前置条件

- 已安装 Claude Code v2.1.196 及以上（本教程用到 `paths` 限定与 `${CLAUDE_PROJECT_DIR}` 替换，低版本可省略）
- 一台装了 `git` 与 `grep`（或 `rg`）的 macOS/Linux 机器，Windows 用户需启用 `CLAUDE_CODE_USE_POWERSHELL_TOOL=1`
- 一个含有环境变量引用的真实项目（Node 的 `process.env.*`、Python 的 `os.environ`/`os.getenv` 均可）用于联调
- 基本的 YAML 与 Markdown 语法知识

## 第一步：理解 Skill 的"渐进式披露"内核

在动手前，必须想清楚一件事：**Skill 不是一段常驻提示词，而是一套按需加载的能力包。**

这与 `CLAUDE.md` 有本质区别。`CLAUDE.md` 的每一行在每次对话都会被读入上下文，属于"固定成本"；而 Skill 采用分层披露：

- **第一层（常驻）**：只有 `name` 与 `description` 进入技能清单，被 Claude 用来判断"现在要不要用我"。这一层在每次会话都加载，所以必须极简——官方把 `description + when_to_use` 合计截断在 **1,536 个字符**。
- **第二层（按需）**：当 Claude 判定相关、或你键入 `/skill-name` 时，`SKILL.md` 的正文才被读入。
- **第三层（深度）**：正文里引用的 `reference.md`、`scripts/` 等支持文件，只在 Claude 真正需要细节时才被读取。

一句话记住设计原则：**首层要"勾得到"，正文要"压得紧"，深水区要"藏得住"。** 长篇参考资料不该污染每次对话的上下文。

> **常见问题**：我把详细规则全塞进 `description`，为什么 Skill 反而不触发？
> **解决**：`description` 超过 1,536 字符会被截断，关键触发词可能被切掉。把"做什么+何时用"压在前两句，细节挪到正文。

## 第二步：用"description 触发工程"想清楚定位

Skill 能否被自动调用，90% 取决于 `description` 写得好不好。写之前，先回答三个问题：

1. **这个 Skill 解决什么具体动作？** —— "扫描代码里的环境变量引用，生成 .env.example 模板"
2. **用户会说什么话时该触发？** —— "我需要哪些环境变量""帮我生成 env 模板""这个项目要配什么变量"
3. **什么情况下绝不该触发？** —— 用户只是在读某个变量值、或在讨论配置架构时

把答案浓缩成一句话描述，把触发短语放进 `when_to_use`。env-scan 的定位草稿：

```yaml
description: Scans a project for environment variable references and generates a .env.example template with a variable inventory. Use when the user asks what env vars they need, wants a .env template, or onboards to a codebase.
when_to_use: 触发短语如 "需要哪些环境变量" "生成 .env 模板" "项目要配什么变量" "onboarding env"
```

注意 `description` 我刻意用英文写主句——技能清单匹配对英文语义更稳；`when_to_use` 里塞中文触发短语，能提升中文场景下的命中率。两种语言混用是实战中常见的取巧手法。

## 第三步：建立目录骨架

Skill 的存放位置决定它的作用域。本教程做一个**个人级 Skill**（跨所有项目可用）：

```bash
mkdir -p ~/.claude/skills/env-scan/scripts
mkdir -p ~/.claude/skills/env-scan/examples
```

得到如下结构（这是官方推荐的多文件布局）：

```text
env-scan/
├── SKILL.md              # 入口指令（必需）
├── reference.md          # 各语言提取规则详解（按需加载）
├── examples/
│   └── sample-output.md  # 期望输出样例
└── scripts/
    └── scan.sh           # 实际执行扫描的脚本
```

官方文档明确：**`SKILL.md` 建议控制在 500 行以内**，详细参考材料拆到同级文件，由正文里写链接告诉 Claude "什么时候去读它"。这就是渐进式披露的落地。

## 第四步：写 SKILL.md——入口要薄，指令要硬

打开 `~/.claude/skills/env-scan/SKILL.md`，写入下面的内容。注意 frontmatter 与正文之间用 `---` 分隔：

```yaml
---
name: env-scan
description: Scans a project for environment variable references and generates a .env.example template with a variable inventory. Use when the user asks what env vars they need, wants a .env template, or onboards to a codebase.
allowed-tools: Bash(grep:*), Bash(rg:*), Read, Write
---

# 环境变量扫描

## 目标
找出当前项目中所有环境变量引用，产出：
1. 一份按"变量名 → 用途推断 → 来源文件"组织的清单（Markdown 表格）
2. 一个可直接使用的 `.env.example`，每行带注释占位

## 输入采集
先执行下面这行（动态上下文注入），把扫描结果内联进上下文：

!`${CLAUDE_SKILL_DIR}/scripts/scan.sh`

## 处理规则
- 变量名去重，按字母序排列
- 同名变量出现在多文件时，合并到一行，来源用逗号分隔
- 用途从最近的赋值/注释/上下文推断，不确定时标注 `(unknown)`
- 标准变量（PORT、NODE_ENV、DATABASE_URL）给出推荐默认值

## 输出
- 在仓库根写入 `.env.example`（已存在则先备份为 `.env.example.bak`）
- 在回复中粘贴清单表格
- 对任何看起来像密钥的变量（含 KEY/SECRET/TOKEN/PASSWORD）额外提醒"勿提交真实值"

## 深度参考
- 各语言的精确提取正则见 [reference.md](reference.md)
- 期望输出格式见 [examples/sample-output.md](examples/sample-output.md)
```

这里有几个**关键机制**值得逐一点破：

- `` !`command` `` 是**动态上下文注入**：Claude Code 会在把正文交给 Claude 之前先执行命令、用输出替换这一行。于是 Claude 拿到的不是"去跑脚本"的指令，而是"带着扫描结果直接开干"的现成上下文。这是 Skill 区别于普通提示词的最强武器。
- `${CLAUDE_SKILL_DIR}` 是字符串替换变量，解析为 Skill 所在目录，保证脚本路径不依赖当前工作目录。
- `allowed-tools` 把权限收紧到"只允许 grep/rg/Read/Write"，既减少授权打断、又构成最小权限围栏。
- 正文末尾的两个链接是给 Claude 看的路标：告诉它"需要正则细节时去读 reference.md"。这两份文件平时不加载，只在 Claude 判断需要时才读——典型的第三层披露。

## 第五步：补齐支持文件

把"重而少用"的内容挪出 `SKILL.md`。先写 `scripts/scan.sh`（脚本是被执行而非被读入的，所以可以写得详尽）：

```bash
#!/usr/bin/env bash
# 扫描常见环境变量引用模式，输出 "变量名<TAB>文件:行号"
set -euo pipefail

ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
cd "$ROOT"

# 用 ripgrep 优先，回落到 grep -rE
if command -v rg >/dev/null 2>&1; then
  GREP="rg --no-heading -n"
else
  GREP="grep -rnE"
fi

{
  $GREP 'process\.env\.([A-Z0-9_]+)'       -g '!node_modules' -g '!.git' . \
    | sed -E 's/.*process\.env\.([A-Z0-9_]+).*/\1/'            || true
  $GREP 'os\.(environ|getenv)\(["\x27]?([A-Z0-9_]+)' -g '!.git' . \
    | sed -E "s/.*(?:environ|getenv)[\"\x27]?([A-Z0-9_]+).*/\1/" || true
  $GREP '\$\{?([A-Z][A-Z0-9_]*)\}? *[:=-]' -g '*.sh' -g '*.env*' -g 'Makefile' . \
    | sed -E 's/.*\$\{?([A-Z][A-Z0-9_]+).*/\1/'                || true
} | sort -u
```

再写一份精简的 `reference.md`，专门放"哪种语言用什么正则"的查表内容，供 Claude 在遇到生僻框架（比如 Go 的 `os.LookupEnv`、Java 的 `System.getenv`）时按需查阅：

```markdown
# 环境变量提取规则速查

| 语言/运行时 | 匹配模式 | 示例 |
|---|---|---|
| Node.js | `process\.env\.([A-Z0-9_]+)` | `process.env.DATABASE_URL` |
| Python | `os\.(?:environ|getenv)\(["']?([A-Z0-9_]+)` | `os.getenv("API_KEY")` |
| Shell | `\$\{?([A-Z][A-Z0-9_]*)\}?` | `${HOME}` |
| Go | `os\.LookupEnv\("([A-Z0-9_]+)"\)` / `os\.Getenv` | `os.LookupEnv("PORT")` |
| Java/Kotlin | `System\.getenv\("([A-Z0-9_]+)"\)` | `System.getenv("JWT_SECRET")` |
| .NET | `Environment\.GetEnvironmentVariable\("([A-Z0-9_]+)"\)` | — |

## 边界情况
- 动态变量名（`process.env[dynamic]`）无法静态提取，报告为 `(dynamic)`
- 测试桩里的假变量（mock）容易误报，优先排除 `__tests__`、`*.spec.*`、`*.test.*`
- `.env.example` 本身也要被解析，作为"已知声明变量"的补充来源
```

最后放一个 `examples/sample-output.md`，给 Claude 一个"好答案长什么样"的锚点，这对输出稳定性帮助极大：

```markdown
## 变量清单（节选）

| 变量名 | 推荐默认/用途 | 来源 |
|---|---|---|
| DATABASE_URL | postgresql://user:pass@host:5432/db | src/db.ts:12 |
| JWT_SECRET | ⚠️ 勿提交真实值，生成 64 位随机串 | src/auth.ts:8 |
| PORT | 3000 | src/server.ts:5, docker-compose.yml:7 |
| NODE_ENV | development \| production | package.json:9 |

## 生成的 .env.example
DATABASE_URL=postgresql://user:pass@host:5432/db
JWT_SECRET=replace-with-64-char-random-string
PORT=3000
NODE_ENV=development
```

## 第六步：用 frontmatter 给 Skill 装"护栏"

回到 `SKILL.md` 的 frontmatter，按需加上控制字段。env-scan 已用了 `allowed-tools`，再补两个高频字段说明用法：

- **`paths`**（v2.1.196+）：限定 Skill 只在处理匹配文件时自动触发。例如做一个"只在前端项目跑"的 Skill，可写 `paths: ["src/**/*.tsx", "package.json"]`。env-scan 故意**不设** `paths`——因为它要跨语言、跨项目通用，任何项目都可能需要它。
- **`disable-model-invocation: true`**：禁止 Claude 自动触发，只能你手动 `/env-scan`。适用于部署、发版这类"绝不能误触"的动作。env-scan 希望能自动触发，所以**不设**此项。
- **`user-invocable: false`**：从 `/` 菜单隐藏，纯做背景知识。本 Skill 要保留手动入口，所以也不设。

判断口诀：**想自动→不设 disable；想手动→设 disable；想隐形→设 user-invocable: false；想限场景→设 paths。**

## 第七步：联调——三种方式验证

Skill 不需要重启 Claude Code，**保存即生效**（官方称为 Live Change Detection）。打开一个含环境变量的项目，启动 `claude`，按下面顺序验证：

**方式 A：自动触发（最该测）**

输入一句贴近 `when_to_use` 的自然语言：

```text
我刚接手这个项目，需要配置哪些环境变量才能跑起来？
```

预期：Claude 自主加载 env-scan，执行扫描脚本，给出清单并生成 `.env.example`。如果没触发，问题几乎都在 `description`——回到第二步重写。

**方式 B：手动调用**

```text
/env-scan
```

预期：直接执行，无需 Claude 判断相关性。这条一定能跑通，可用于隔离"是脚本坏了还是描述没写好"。

**方式 C：带参数调用**（若声明了 `arguments`）

若把 frontmatter 加上 `arguments: [path]`，正文里用 `$0` 或 `$path` 引用，就能 `/env-scan src/` 限定扫描范围。

> **常见问题**：方式 A 不触发，方式 B 正常。
> **排查清单**：① `description` 是否前两句就点明"做什么+何时用"；② 是否被 1,536 字符截断；③ `when_to_use` 的触发短语是否贴合你真实的问法；④ 是否误设了 `paths` 把当前文件挡在门外；⑤ 项目里确实有环境变量吗（脚本空输出时 Claude 可能判为无关）。

## 第八步：用官方 skill-creator 做评测与迭代

写到能跑只是及格。要稳定，得做**触发评测**。Claude Code 内置 `/skill-creator`（bundled skill）支持为 Skill 跑 eval：准备一批"应该触发"和"不该触发"的问法，看命中率与误报率，再据此微调 `description`。

迭代重点永远是 `description`：把漏触发的真实问法补进 `when_to_use`，把误触发的场景用更精确的措辞排除。这是一个"写-测-改"的小循环，通常 2–3 轮就能收敛。

---

## 实战案例：在一个真实 Node 项目里跑通 env-scan

以一个用了 `process.env` 的 Express 项目为例。在项目根目录启动 Claude Code 后，输入"帮我生成环境变量模板"。完整时序如下：

1. Claude 匹配到 env-scan 的 `description`，决定加载该 Skill
2. 读入 `SKILL.md`，遇到 `` !`${CLAUDE_SKILL_DIR}/scripts/scan.sh` ``，先执行脚本
3. 脚本 ripgrep 扫描 `src/`，输出 `DATABASE_URL\nJWT_SECRET\nPORT\n...`
4. 扫描结果替换进正文，Claude 据此生成 Markdown 清单
5. Claude 调用 `Write` 写出 `.env.example`（发现已存在则备份）
6. 在回复中贴出清单，并对 `JWT_SECRET` 给出"勿提交真实值"的安全提醒

整个流程对你而言是一句话，对 Claude 而言是"加载能力→采数据→产出→落盘"的完整闭环。这正是 Skill 把"重复多步操作"压成"一句话指令"的价值。

```mermaid
flowchart TD
    U[用户：帮我生成 env 模板] --> M{Claude 匹配 description?}
    M -->|命中| L[加载 SKILL.md 正文]
    M -->|未命中| N[不触发，普通对话]
    L --> I[动态注入：执行 scan.sh]
    I --> S[扫描结果内联进上下文]
    S --> R{需要正则细节?}
    R -->|是| D[读 reference.md 第三层]
    R -->|否| G[直接生成清单]
    D --> G
    G --> W[Write 写出 .env.example]
    W --> O[回复清单 + 安全提醒]

    style M fill:#fef3c7,stroke:#d97706
    style I fill:#dbeafe,stroke:#2563eb
    style W fill:#dcfce7,stroke:#16a34a
```

```mermaid
flowchart LR
    subgraph 第一层[常驻 · 每次会话加载]
        A1[name] --- A2[description / when_to_use ≤1536 字符]
    end
    subgraph 第二层[按需 · 触发时加载]
        B1[SKILL.md 正文]
    end
    subgraph 第三层[深度 · Claude 主动读取]
        C1[reference.md] --- C2[examples/] --- C3[scripts/]
    end
    第一层 -->|相关/手动调用| 第二层
    第二层 -->|需要细节| 第三层

    style 第一层 fill:#eff6ff
    style 第二层 fill:#fef9c3
    style 第三层 fill:#f3e8ff
```

## 进阶：把 Skill 做成可分发的插件

当 env-scan 稳定后，你可能想分发给团队。两种途径：

1. **提交到项目仓库**：把 `.claude/skills/env-scan/` 整个目录提交，团队成员 clone 后自动可用。注意 `.claude/skills/` 是项目级作用域，仅对该项目生效。
2. **打包为插件**：在 Skill 目录加一个 `.claude-plugin/plugin.json`，它就升级为可挂载 agents、hooks、MCP 服务器的插件包，通过 marketplace 分发，并用 `plugin-name:skill-name` 命名空间避免冲突。

分发前务必检查 `scripts/` 里没有硬编码的本地路径，统一用 `${CLAUDE_SKILL_DIR}` 与 `${CLAUDE_PROJECT_DIR}` 保持可移植。

## 延伸阅读

- **官方 Skill 文档**：<https://code.claude.com/docs/en/skills> —— frontmatter 字段、字符串替换、触发控制的权威来源
- **Agent Skills 开放标准**：<https://agentskills.io> —— Skill 跨工具互通的规范基线
- **skill-creator 内置技能**：在 Claude Code 中键入 `/skill-creator`，引导式创建并跑触发评测
- **CLAUDE.md 写作指南**：Skill 正文同样适用"陈述要做什么，而非叙述怎么做"的精简原则

## 训练营适用性评估

- **AI 工程化训练营**（强烈推荐）：作为"提示词工程→能力沉淀"的核心实操，4 课时。第一课时讲渐进式披露机制，第二课时手搓 env-scan，第三课时做触发评测，第四课时打包分发。
- **企业研发效能训练营**（推荐）：定位为"团队知识资产化"案例，2 课时。重点演示如何把团队规范、checklist 沉淀为项目级 Skill，统一 AI 辅助质量基线。
- **零基础入门训练营**（不推荐）：本教程涉及权限、脚本、正则与触发调优，前置门槛偏高，建议放在学员已熟悉基础用法之后。
- **预计教学时长**：讲师演示 45 分钟 + 学员独立完成一个自选 Skill 90 分钟 + 评测调优 30 分钟，合计约 3.5 小时。

## 参考来源

1. [Extend Claude with skills — Claude Code 官方文档](https://code.claude.com/docs/en/skills) — Skill 创建、frontmatter 字段全表、渐进式披露与触发控制的权威定义
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 对全部 frontmatter 字段（含 disable-model-invocation、allowed-tools、paths 等）的逐项图解
3. [How to Create Custom Claude Code Skills: Complete Tutorial (2026)](https://www.aiforanything.io/blog/how-to-create-custom-claude-code-skills-tutorial-2026) — SKILL.md 结构与从零创建流程的社区教程
4. [claude-code-skills.md — gregpriday/claude-code-docs](https://github.com/gregpriday/claude-code-docs/blob/main/claude-code-skills.md) — 基于渐进式披露原则的 Skill 文件系统能力机制解读
5. [Agent Skills 开放标准](https://agentskills.io) — Skill 跨 AI 工具互通的规范，Claude Code Skills 遵循该标准并加以扩展

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
