dontbesilent2025/dbskill已通过检查
SKILL DETAIL
dbs-skill-maker
dontbesilent2025/dbskill/dbs-skill-maker
dbs-skill-maker 是一个用于制作单个 Skill 的工具,它通过需求分析将用户反复遇到的问题转化为可安装、可分级验证的 Skill。该工具强调从问题出发,保留用户选择,按需引入理论,直接生成文件,保持主入口轻量,用行为验证,失败驱动修改,外部动作单独授权,并鼓励用户参与需求分析。 该工具提供了一套完整的流程,包括形成问题契约、行为契约、选择机制、生成候选、分级验证候选,以及本地交付和可选的 GitHub 发布。它支持结构校验、行为冒烟测试、留出/回归测试和安装交付四个验证等级,确保 Skill 的质量和可用性。
安装量 · 224查看来源
Installation
npx skills add https://github.com/dontbesilent2025/dbskill --skill dbs-skill-maker
技能文件
SKILL.md
最近同步 · 2026年8月29日
agents/openai.yaml›
interface:
display_name: "dbs-skill-maker"
short_description: "把反复问题制作成可安装、可分级验证的单个 Skill"
default_prompt: "使用 $dbs-skill-maker 和我一起分析反复遇到的问题,并制作成可安装、验证等级清楚的单个 Skill。"
references/evaluation.md›
# 行为评测与改进
当候选 Skill 已经存在,或者用户提供了失败输出和反馈时读取本文件。
## 行为契约先于样本
评测先固定目标任务、必须行为、关键失败和允许变化。测试只观察输出、工具动作、边界与停止条件,不规定隐藏推理过程。
## 验证等级
| 等级 | 必需证据 | 不能替代它的证据 |
| --- | --- | --- |
| 1.结构校验 | 静态校验器通过 | 文件看起来完整 |
| 2.行为冒烟 | 至少 1 个真实主任务的实际输出通过评分 | 正则匹配到某个标题 |
| 3.留出/回归 | 盲测的留出与回归样本通过,无关键回归 | 执行者看过预期答案的试跑 |
| 4.安装交付 | GitHub 来源安装成功,且文件内容与源目录逐项一致 | README 里已经写了安装命令 |
报告最高已完成等级,同时列出下一级尚缺的证据。
## 最小评估组合
| 样本类型 | 检查内容 |
| --- | --- |
| 正常正例 | 主要任务是否完成 |
| 边界样本 | 缺少关键输入、权限不足或风险升高时能否收窄、追问或停止 |
| 近邻反例 | 相似请求是否会误触发或越界处理 |
| 留出样本 | 修改能否迁移到未参与设计的任务 |
| 回归样本 | 旧版本已经通过的关键能力是否仍然保留 |
新 Skill 先使用 3~6 个样本。高风险或准备公开发布时扩大覆盖。
## 样本格式
将本地样本保存在目标 Skill 的 `evals/evals.json`,默认不发布:
```json
{
"skill": "skill-name",
"behavior_contract": {
"must": ["可观察行为"],
"must_not": ["关键失败"]
},
"cases": [
{
"id": "normal-01",
"type": "normal",
"prompt": "用户原始请求",
"input_files": [],
"expected_behavior": ["只交给评分者的验收标准"]
}
]
}
```
每次执行把结果记录在 `evals/results/<candidate-id>.json`,至少包含基线、候选、环境、逐样本结果、回归和最高验证等级。候选标识必须能指向一个文件快照或 Git commit,不使用“最新版”一类无法恢复的标识。
执行者只能读取 `prompt` 和必要输入文件。`expected_behavior` 交给评分者,不能提前泄露给执行者。
## 运行基线或候选
记录:
- 实际输出和工具动作;
- 每条行为契约是否满足;
- 失败发生的具体步骤;
- 权限、网络、文件和依赖状态;
- 模型或环境差异;
- 时间和 token,环境可取得时再记录。
新 Skill 没有旧版本时,把首个候选作为起点,检查它是否达到行为契约。已有 Skill 修改时,使用相同任务和环境比较基线与候选。
## 失败归因
每个失败先确定一个主要来源:
| 来源 | 处理 |
| --- | --- |
| Skill 指令缺陷 | 修改决策条件、步骤、reference 或 script |
| 触发描述缺陷 | 调整 `description` 的用途和使用条件 |
| 输入缺口 | 增加必要输入或最小追问条件 |
| 样本缺陷 | 修订冲突、失真或泄露答案的测试 |
| 评分缺陷 | 改写与真实任务无关的断言 |
| 工具环境故障 | 修复权限、依赖或打包 |
| 执行波动 | 重跑并降低结论强度 |
## 最小修改假设
对准备修改的问题记录:
```text
观察到的失败 → 推测原因 → 修改位置 → 预期改善 → 可能回归
```
优先补清决策与停止条件、调整步骤传递、增加必要检查点、沉淀确定性脚本、删除冲突规则。最后才增加更长的规则清单。
## 保留候选
候选可以保留,需要同时满足:
- 原始失败得到改善;
- 留出样本支持迁移;
- 没有任务边界、安全、事实或权限方面的关键回归;
- 收益没有依赖答案泄露或单个案例补丁;
- 新增成本与收益相称。
证据不足时写“继续试验”。候选整体退化时恢复快照,并保留失败记录。
references/github-publishing.md›
# GitHub 可选发布
只有用户明确要求发布、分享、创建 GitHub 仓库或生成 `npx skills add` 安装命令时读取本文件。
本地 Skill 已经完成时,可以提醒一次发布能力。用户未选择发布时,不创建 GitHub 配置、README、仓库、commit、tag 或 Release。
## 发布前需要确定
优先从上下文和当前 Git 状态取得:
- 使用现有仓库还是创建新仓库;
- 仓库所有者与名称;
- 公开或私有;
- 默认分支;
- 是否只提交当前 Skill;
- 是否需要版本 tag 或 GitHub Release。
只有缺少的信息会改变远端目标、公开范围或版本策略时才问 1 个问题。
## 授权边界
“帮我准备发布”只覆盖本地仓库准备和校验。“发布到 GitHub”“创建仓库并推送”等明确表达可以覆盖对应远端动作。授权不清楚时,在 push、tag 或 Release 前停止并说明将发生什么。
不得自动发布用户未审查的私密样本、测试答案、运行记录、密钥、内部路径和本机配置。
## 单 Skill 仓库结构
默认准备:
```text
repository/
├── README.md
└── skills/
└── skill-name/
├── SKILL.md
├── agents/ 存在时复制
├── references/ 存在时复制
├── scripts/ 存在时复制
└── assets/ 存在时复制
```
`evals/`、缓存、临时文件和本机隐藏文件默认排除。公开测试确有用户价值时,先脱敏并让用户确认具体文件。
可以使用:
```bash
python3 scripts/prepare_github_repo.py <skill-directory> <staging-directory> --owner <owner> --repo <repo>
```
脚本校验 owner 和 repo 格式,逐项比较打包前后文件,并从 `description`、`agents/openai.yaml` 和实际脚本生成 README 的用途、使用示例与依赖。它只准备本地 staging 目录,不创建远端仓库,也不 push。
## README 最小内容
- Skill 解决的问题;
- 适用条件和主要边界;
- 安装命令;
- 最短使用示例;
- 实际需要的工具或环境依赖;
- 隐私或权限说明,确有需要时加入。
单 Skill 仓库默认安装命令:
```bash
npx -y skills add <owner>/<repo> -g --all
```
## 发布门禁
发布前检查:
1. 运行 Skill 结构校验与脚本测试;
2. 检查 Git diff、未跟踪文件和暂存区;
3. 检查密钥、个人路径、私密材料和 `evals/`;
4. 只使用明确路径暂存,禁止 `git add .` 和 `git add -A`;
5. commit 信息指向实际用户收益;
6. push 前再次确认仓库、分支和提交范围;
7. tag 或 Release 只在用户要求时创建。
发现删除、覆盖、远端分叉、已有 tag、疑似密钥或额外暂存文件时停止,将异常合并说明。
## 安装验证
README 中存在命令只能证明说明已生成。发布后需要在隔离环境运行:
```bash
bash scripts/verify_npx_install.sh <owner>/<repo> <skill-name> <source-skill-directory>
```
验证至少包括:
- `npx skills add` 能发现并安装仓库中的 Skill;
- 安装目录存在 `SKILL.md`;
- frontmatter `name` 与预期一致;
- `SKILL.md`、agents、references、scripts 和 assets 与源目录逐文件一致。
网络、GitHub 权限或安装器故障时保留本地 Skill,报告失败发生在哪一步,不反复创建仓库或重复 push。
references/problem-and-goal.md›
# 问题契约与完成条件
当用户只有想法、功能名称或模糊愿望时读取本文件。目标是确定新 Skill 反复解决的问题,以及什么证据能够证明它完成了任务。
## 任务判断
先提取下面的变化链:
```text
使用情境 → 当前卡住的进展 → 希望发生的变化 → Skill 交付物 → 完成证据
```
使用任务陈述:
> 当我处于 `{情境}`,我想让 Agent `{推进的变化}`,以便 `{得到的结果}`。
“推进的变化”要描述状态变化。“整理资料”“写提示词”“创建 Skill”通常只是候选手段,需要继续判断用户完成后能得到什么。
## 最小问题契约
```markdown
## 问题契约
反复出现的问题:
典型使用情境:
当前方案及其稳定失败:
用户想推进的变化:
Skill 需要交付的结果:
完成证据:
可接受代价:
不处理的近邻问题:
仍待验证的事实:
```
默认用户可以参与需求分析。对问题契约、完成证据和近邻边界存在的分歧,可以邀请用户判断,并说明该判断将影响哪个 Skill 设计。缺少材料时先给“待验证假设”,继续交付当前可用版本。合并相关问题,不询问从上下文已经得到答案的内容。
## 是否值得做成 Skill
至少检查以下特征:
| 特征 | 可沉淀信号 | 暂缓信号 |
| --- | --- | --- |
| 重复性 | 同类问题会再次出现 | 完全一次性的现场任务 |
| 稳定动作 | 可以提炼判断、转换或执行动作 | 每次都依赖无法归纳的临场决定 |
| 可观察结果 | 能看到文件、动作、判断或状态 | 只能描述抽象感受 |
| 输入可得 | 必要材料和权限能够取得 | 关键事实长期不可获得 |
| 边界可写 | 能识别适用与退出条件 | 希望处理所有相邻问题 |
| 复用收益 | 下次调用能减少判断或操作成本 | 制作成本持续高于重复收益 |
不满足全部特征时,可以收窄任务。保留能稳定复用的部分,避免为单个案例生成过窄规则。
## 行为契约
问题契约已经可用后,形成:
```markdown
## 行为契约
目标任务:
预期使用条件:
输入变化范围:
必须做到:
- ...
禁止出现:
- ...
允许变化:
- ...
关键失败:
- ...
```
“必须做到”只写可观察输出或动作。“允许变化”用于保留模型对开放任务的判断空间。“关键失败”优先覆盖任务错位、安全、事实、权限和近邻边界。
## 完成条件
问题阶段可以在下面条件满足时结束:
- 下一步动作能够确定;
- 候选 Skill 的交付物能够确定;
- 用户或测试者能够识别完成;
- 近邻边界足以避免明显误触发;
- 未知项不会改变当前候选的主要结构。
references/skill-construction.md›
# Skill 构建规范
当问题契约、行为契约和关键机制已经足够清楚,需要创建或实质修改 Skill 文件时读取本文件。
## 选择最小结构
每个 Skill 必须有 `SKILL.md`。其他目录按实际用途添加:
- `agents/openai.yaml`:Codex UI 元数据或调用策略;
- `references/`:只在特定条件下读取的规则、标准、模式和案例;
- `scripts/`:重复、确定且需要可靠执行的操作;
- `assets/`:会复制或进入最终交付的模板、图片、字体和项目骨架。
不创建空目录、占位 README、更新日志和重复说明。GitHub 打包需要安装说明时再生成 README。
## 命名与发现
- 名称使用小写英文、数字和连字符,少于 64 个字符;
- 目录名与 frontmatter `name` 一致;
- `description` 同时说明能力和使用条件;
- `description` 遵守 1~1,024 个字符规范,建议尽量控制在 300 个字符内;
- 相邻任务容易误触发时,增加一个有信息量的边界;
- 默认允许正常自动发现,用户明确要求显式调用时再关闭隐式调用。
## `SKILL.md` 写什么
主入口保留:
- 任务和产出;
- 关键不变量;
- 状态或模式选择标准;
- 必要输入和最少提问条件;
- 核心工作方式;
- 授权、停止与安全边界;
- references 和 scripts 的读取或运行条件。
入口超过约 300 行时检查是否存在可以按条件移入 reference 的大段内容。这里是上下文预算提醒,没有平台行数硬限制。
避免写入模型已经具备的通用建议、无法验证的口号、单个案例答案和对所有任务都生效的细碎规则。
## 脚本判断
满足以下任一条件时考虑脚本:
- 同一转换会反复重写;
- 文件操作需要确定结果;
- API 调用、数据处理或校验需要稳定参数;
- 纯指令执行容易漏掉关键检查。
脚本必须验证成功路径和至少一个失败路径。涉及写入、网络、发布或删除时,保留清楚的授权和停止条件。
## 初始化与交付的边界
`init_skill_project.py` 需要任务、工作动作和完成证据,以便初始文件不含空占位符。这些参数只构成候选初稿,Agent 仍需要:
1. 根据问题契约补足适用条件、边界和停止条件;
2. 删除没有改变行为的通用句子;
3. 扫描整个目录的占位符、本机绝对路径和敏感值;
4. 完成静态校验和当前能够执行的行为测试。
只有结构校验时,交付报告必须写明“行为尚未验证”。
## UI 元数据
`agents/openai.yaml` 中:
- 字符串全部加引号;
- `display_name` 使用稳定名称;
- `short_description` 简短说明独有能力;
- `default_prompt` 使用 1 句话,并显式包含 `$skill-name`;
- 只有真实依赖 MCP 工具时才声明 `dependencies`;
- 未经用户明确要求,不设置 `allow_implicit_invocation: false`。
## 完成前检查
1. `description` 能区分当前任务和近邻任务;
2. 所有相对引用真实存在;
3. scripts 已运行验证;
4. 没有未完成占位符;
5. 用户的明确选择和授权边界得到保留;
6. 私密信息没有写进可分发文件;
7. 入口只加载当前任务需要的共同信息。
使用本 Skill 自带的校验器:
```bash
python3 scripts/validate_skill_project.py /absolute/path/to/skill
```
references/theory-and-mechanism.md›
# 理论与机制设计
当问题已经清楚,但 Skill 的关键判断缺少可靠机制、适用条件或反证方式时读取本文件。
理论只服务于具体动作。一个理论、行业标准或经验规则进入 Skill,需要改变至少一项:判断顺序、分类标准、中间产物、适用边界或验证方法。
## 先拆判断动作
```markdown
| 判断动作 | 输入 | 需要得到的中间结果 | 缺少它会发生什么错误 |
| --- | --- | --- | --- |
```
常见动作包括:定义、归类、找因果、识别任务、分层、比较方案、找反例、生成方案、验证现实、决定停止。
## 机制候选
每个动作提出最少数量的候选机制:
```markdown
| 动作 | 候选理论/规则/工具 | 为什么适合 | 可能失效的条件 | 核实状态 |
| --- | --- | --- | --- | --- |
```
优先级:
1. 用户已经验证的现实规则或明确行业标准;
2. 能直接解释当前判断动作的可信理论;
3. 多个独立案例反复出现的条件性机制;
4. 可证伪、可替换的暂定假设。
名人辨识度和术语新鲜感不能单独构成入选理由。
## 核实要求
外部事实会改变 Skill 的判断时才进行外部研究。优先使用原著、原始论文、官方标准和权威机构材料。无法核实原始来源时标注权威二手来源或尚未核实,不猜测作者、年份、引文和页码。
研究简单问题时保留:
- 1 个主机制;
- 0~2 个补充机制;
- 至少 1 个纠偏方向或失败边界。
候选很多时,根据机制匹配、来源可靠、能否导出行为、边界清晰度和信息增量筛选。
## 映射到 Skill
```markdown
| Skill 步骤 | 使用机制 | 输入 | 输出 | 拦截的错误 | 失效边界 |
| --- | --- | --- | --- | --- | --- |
```
每个机制必须落到具体步骤。多个机制可以顺序执行、并行分析或分层检验,需要说明中间结果怎样传递。
## 小样本试跑
将候选机制用于一个真实片段,记录:
- 试跑输入;
- 每一步产生的中间判断;
- 比常识新增的判断;
- 能导出的行动;
- 能够推翻它的信号。
跑不出新判断、无法导出行动或无法设置失败信号的机制,从候选 Skill 中删除。理论研究结束后只把实际改变 Agent 行为的部分写入 Skill。
scripts/init_skill_project.py›
#!/usr/bin/env python3
"""Create a minimal single-Skill project without overwriting existing files."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
NAME_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
RESOURCE_NAMES = {"references", "scripts", "assets"}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="创建单个 Skill 的最小项目骨架")
parser.add_argument("name", help="Skill 名称,小写英文、数字和连字符")
parser.add_argument("--output", required=True, help="Skill 父目录")
parser.add_argument("--description", required=True, help="能力与使用条件")
parser.add_argument("--task", required=True, help="反复解决的问题与交付结果")
parser.add_argument(
"--workflow",
action="append",
required=True,
help="一个关键工作动作,可重复传入",
)
parser.add_argument(
"--done",
action="append",
required=True,
help="一条可观察的完成证据,可重复传入",
)
parser.add_argument(
"--resources",
default="",
help="按需创建 references,scripts,assets,使用英文逗号分隔",
)
parser.add_argument("--short-description", default="", help="Codex UI 短描述")
parser.add_argument("--explicit-only", action="store_true", help="只允许显式调用")
return parser.parse_args()
def yaml_string(value: str) -> str:
return json.dumps(value, ensure_ascii=False)
def main() -> int:
args = parse_args()
name = args.name.strip()
description = " ".join(args.description.split())
task = " ".join(args.task.split())
workflow = [" ".join(item.split()) for item in args.workflow if item.strip()]
done = [" ".join(item.split()) for item in args.done if item.strip()]
if not NAME_PATTERN.fullmatch(name) or len(name) >= 64:
raise SystemExit("名称必须少于 64 个字符,并只使用小写英文、数字和连字符")
if not 1 <= len(description) <= 1024:
raise SystemExit("description 必须在 1~1,024 个字符之间")
if not task:
raise SystemExit("task 不能为空")
if not workflow:
raise SystemExit("至少需要一个 workflow")
if not done:
raise SystemExit("至少需要一条 done")
resources = {item.strip() for item in args.resources.split(",") if item.strip()}
unknown = sorted(resources - RESOURCE_NAMES)
if unknown:
raise SystemExit(f"未知资源目录:{', '.join(unknown)}")
short_description = args.short_description.strip() or f"把明确问题制作成可验证的 {name} Skill"
if not 25 <= len(short_description) <= 64:
raise SystemExit("short-description 应在 25~64 个字符之间")
target = Path(args.output).expanduser().resolve() / name
if target.exists():
raise SystemExit(f"目标已经存在,未覆盖:{target}")
target.mkdir(parents=True)
agents_dir = target / "agents"
agents_dir.mkdir()
for resource in sorted(resources):
(target / resource).mkdir()
workflow_lines = "\n".join(f"{index}. {item}" for index, item in enumerate(workflow, 1))
done_lines = "\n".join(f"- {item}" for item in done)
skill_text = f"""---
name: {name}
description: {description}
---
# {name}
## 任务
{task}
## 工作方式
{workflow_lines}
## 完成条件
{done_lines}
"""
(target / "SKILL.md").write_text(skill_text, encoding="utf-8")
openai_lines = [
"interface:",
f" display_name: {yaml_string(name)}",
f" short_description: {yaml_string(short_description)}",
f" default_prompt: {yaml_string(f'使用 ${name} 完成它所解决的任务,并按可观察标准检查结果。')}",
]
if args.explicit_only:
openai_lines.extend(["policy:", " allow_implicit_invocation: false"])
(agents_dir / "openai.yaml").write_text("\n".join(openai_lines) + "\n", encoding="utf-8")
print(f"已创建:{target}")
print("下一步:补全适用条件、边界和停止条件,再运行 validate_skill_project.py。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
scripts/prepare_github_repo.py›
#!/usr/bin/env python3
"""Prepare a local staging directory for a single-Skill GitHub repository."""
from __future__ import annotations
import argparse
import filecmp
import re
import shutil
from pathlib import Path
FRONTMATTER_PATTERN = re.compile(r"\A---\s*\n(.*?)\n---(?:\s*\n|\Z)", re.DOTALL)
ALLOWED_ENTRIES = ("SKILL.md", "agents", "references", "scripts", "assets")
EXCLUDED_NAMES = {".DS_Store", "__pycache__", "evals"}
OWNER_PATTERN = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$")
REPO_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$")
def read_scalar(frontmatter: str, field: str) -> str:
lines = frontmatter.splitlines()
prefix = f"{field}:"
for index, line in enumerate(lines):
if not line.startswith(prefix):
continue
value = line[len(prefix) :].strip()
if value not in {"|", ">"}:
return value.strip("\"'")
content: list[str] = []
for continuation in lines[index + 1 :]:
if continuation and not continuation[0].isspace():
break
content.append(continuation[2:] if continuation.startswith(" ") else continuation)
return ("\n" if value == "|" else " ").join(content).strip()
return ""
def copy_public_tree(source: Path, destination: Path) -> None:
if source.is_symlink():
raise SystemExit(f"拒绝复制软链,请先确认真实来源:{source}")
if source.is_file():
if source.name not in EXCLUDED_NAMES and source.suffix != ".pyc":
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
return
destination.mkdir(parents=True, exist_ok=True)
for child in sorted(source.iterdir()):
if child.name in EXCLUDED_NAMES or child.suffix == ".pyc":
continue
copy_public_tree(child, destination / child.name)
def public_files(root: Path) -> dict[Path, Path]:
files: dict[Path, Path] = {}
for entry_name in ALLOWED_ENTRIES:
entry = root / entry_name
if not entry.exists():
continue
candidates = [entry] if entry.is_file() else sorted(entry.rglob("*"))
for candidate in candidates:
if candidate.is_symlink():
raise SystemExit(f"拒绝打包软链接:{candidate}")
if not candidate.is_file():
continue
if any(part in EXCLUDED_NAMES for part in candidate.relative_to(root).parts):
continue
if candidate.suffix == ".pyc":
continue
files[candidate.relative_to(root)] = candidate
return files
def read_default_prompt(source: Path, name: str) -> str:
openai_path = source / "agents" / "openai.yaml"
if openai_path.is_file():
match = re.search(r'^\s+default_prompt:\s*["\'](.*)["\']\s*$', openai_path.read_text(encoding="utf-8"), re.MULTILINE)
if match:
return match.group(1)
return f"使用 ${name} 完成它所解决的任务。"
def detect_requirements(source: Path) -> list[str]:
requirements = ["支持 Agent Skills 的客户端"]
scripts = source / "scripts"
if scripts.is_dir() and any(scripts.rglob("*.py")):
requirements.append("Python 3(用于运行 Skill 内脚本)")
if scripts.is_dir() and any(scripts.rglob("*.sh")):
requirements.append("Bash(用于运行 Skill 内脚本)")
openai_path = source / "agents" / "openai.yaml"
if openai_path.is_file():
text = openai_path.read_text(encoding="utf-8")
for value in re.findall(r'^\s+value:\s*["\']?([^"\'\s]+)', text, re.MULTILINE):
requirements.append(f"工具依赖:{value}")
return requirements
def main() -> int:
parser = argparse.ArgumentParser(description="准备单 Skill GitHub 仓库的本地 staging 目录")
parser.add_argument("skill_directory")
parser.add_argument("staging_directory")
parser.add_argument("--owner", required=True)
parser.add_argument("--repo", required=True)
args = parser.parse_args()
source = Path(args.skill_directory).expanduser().resolve()
staging = Path(args.staging_directory).expanduser().resolve()
owner = args.owner.strip()
repo = args.repo.strip()
if not source.is_dir():
raise SystemExit(f"源 Skill 目录不存在:{source}")
if not OWNER_PATTERN.fullmatch(owner):
raise SystemExit("GitHub owner 格式无效")
if not REPO_PATTERN.fullmatch(repo) or repo in {".", ".."}:
raise SystemExit("GitHub repo 格式无效")
skill_path = source / "SKILL.md"
if not skill_path.is_file():
raise SystemExit(f"源目录缺少 SKILL.md:{source}")
if staging.exists():
if not staging.is_dir():
raise SystemExit(f"staging 路径已存在且不是目录:{staging}")
if any(staging.iterdir()):
raise SystemExit(f"staging 目录非空,未覆盖:{staging}")
text = skill_path.read_text(encoding="utf-8")
match = FRONTMATTER_PATTERN.match(text)
if match is None:
raise SystemExit("SKILL.md 缺少合法 frontmatter")
name = read_scalar(match.group(1), "name")
description = " ".join(read_scalar(match.group(1), "description").split())
if not name or not description:
raise SystemExit("SKILL.md 缺少 name 或 description")
target_skill = staging / "skills" / name
source_files = public_files(source)
staging.mkdir(parents=True, exist_ok=True)
for entry_name in ALLOWED_ENTRIES:
entry = source / entry_name
if entry.exists():
copy_public_tree(entry, target_skill / entry_name)
copied_files = public_files(target_skill)
if set(source_files) != set(copied_files):
missing = sorted(str(path) for path in set(source_files) - set(copied_files))
extra = sorted(str(path) for path in set(copied_files) - set(source_files))
raise SystemExit(f"打包资源清单不一致;缺少:{missing};多出:{extra}")
for relative, source_file in source_files.items():
if not filecmp.cmp(source_file, copied_files[relative], shallow=False):
raise SystemExit(f"打包后文件内容不一致:{relative}")
locator = f"{owner}/{repo}"
usage = read_default_prompt(source, name)
requirement_lines = "\n".join(f"- {item}" for item in detect_requirements(source))
readme = f"""# {name}
{description}
## 安装
```bash
npx -y skills add {locator} -g --all
```
## 使用
安装后,可以这样开始:
> {usage}
## 运行依赖
{requirement_lines}
## 内容
本仓库只发布运行这个 Skill 所需的文件。本地评测样本、预期答案和运行记录不包含在公开包中。
"""
(staging / "README.md").write_text(readme, encoding="utf-8")
(staging / ".gitignore").write_text(
".DS_Store\n__pycache__/\n*.pyc\nevals/\n",
encoding="utf-8",
)
print(f"已准备:{staging}")
print(f"安装命令:npx -y skills add {locator} -g --all")
print("尚未创建远端仓库,也没有执行 git push。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
scripts/validate_skill_project.py›
#!/usr/bin/env python3
"""Validate observable structure and references for one Skill directory."""
from __future__ import annotations
import argparse
import re
import subprocess
from pathlib import Path
NAME_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
FRONTMATTER_PATTERN = re.compile(r"\A---\s*\n(.*?)\n---(?:\s*\n|\Z)", re.DOTALL)
LINK_PATTERN = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
TEXT_SUFFIXES = {".md", ".json", ".yaml", ".yml", ".py", ".sh", ".txt"}
UNFINISHED_MARKERS = ("[TODO", "TODO:", "FIXME", "{说明", "{写入", "{可观察", "<待填写>")
LOCAL_PATH_PATTERN = re.compile(r"(?:/Users/[^/\s]+/|/home/[^/\s]+/|[A-Za-z]:\\\\Users\\\\[^\\\s]+\\\\)")
SECRET_PATTERN = re.compile(
r"(?i)(?:api[_-]?key|access[_-]?token|secret|password)\s*[:=]\s*['\"]?([A-Za-z0-9_\-]{16,})"
)
ALLOWED_ROOT_ENTRIES = {"SKILL.md", "agents", "references", "scripts", "assets", "evals"}
def read_scalar(frontmatter: str, field: str) -> str:
lines = frontmatter.splitlines()
prefix = f"{field}:"
for index, line in enumerate(lines):
if not line.startswith(prefix):
continue
value = line[len(prefix) :].strip()
if value not in {"|", ">"}:
return value.strip("\"'")
content: list[str] = []
for continuation in lines[index + 1 :]:
if continuation and not continuation[0].isspace():
break
content.append(continuation[2:] if continuation.startswith(" ") else continuation)
return ("\n" if value == "|" else " ").join(content).strip()
return ""
def read_text(path: Path, errors: list[str]) -> str | None:
try:
return path.read_text(encoding="utf-8")
except UnicodeDecodeError:
errors.append(f"文本文件不是 UTF-8:{path}")
return None
def check_markdown_links(root: Path, path: Path, text: str, errors: list[str]) -> None:
for link in LINK_PATTERN.findall(text):
target = link.split("#", 1)[0].strip().strip("<>")
if not target or target.startswith(("http://", "https://", "mailto:", "#")):
continue
if target.startswith("/"):
errors.append(f"引用使用了绝对路径:{path.relative_to(root)} -> {target}")
continue
resolved = (path.parent / target).resolve()
if root not in resolved.parents and resolved != root:
errors.append(f"引用越出 Skill 目录:{path.relative_to(root)} -> {target}")
elif not resolved.exists():
errors.append(f"引用不存在:{path.relative_to(root)} -> {target}")
def main() -> int:
parser = argparse.ArgumentParser(description="校验单个 Skill 项目")
parser.add_argument("skill_directory")
args = parser.parse_args()
root = Path(args.skill_directory).expanduser().resolve()
errors: list[str] = []
warnings: list[str] = []
skill_path = root / "SKILL.md"
if not root.is_dir():
print(f"ERROR:目录不存在:{root}")
return 1
if not skill_path.is_file():
print(f"ERROR:缺少 {skill_path}")
return 1
text = skill_path.read_text(encoding="utf-8")
match = FRONTMATTER_PATTERN.match(text)
if match is None:
errors.append("SKILL.md 缺少合法的 YAML frontmatter")
frontmatter = ""
else:
frontmatter = match.group(1)
name = read_scalar(frontmatter, "name")
description = read_scalar(frontmatter, "description")
if not NAME_PATTERN.fullmatch(name) or len(name) >= 64:
errors.append("name 必须少于 64 个字符,并只使用小写英文、数字和连字符")
if name and name != root.name:
errors.append(f"目录名 {root.name!r} 与 name {name!r} 不一致")
if not 1 <= len(description) <= 1024:
errors.append(f"description 长度为 {len(description)},规范范围是 1~1,024")
elif len(description) > 300:
warnings.append(f"description 长度为 {len(description)},建议检查是否可以收紧")
if description and not any(token in description.lower() for token in ("时使用", "用户", "当", "when", "use for", "used for")):
warnings.append("description 可能只说明了能力,建议补充使用条件")
for path in sorted(root.rglob("*")):
relative = path.relative_to(root)
if path.is_symlink():
errors.append(f"不允许悬空或外部软链接:{relative}")
continue
if path.is_dir():
if not any(path.iterdir()):
warnings.append(f"空目录没有产生功能:{relative}")
continue
if path.suffix.lower() not in TEXT_SUFFIXES:
continue
file_text = read_text(path, errors)
if file_text is None:
continue
is_validator_source = path.resolve() == Path(__file__).resolve()
if not is_validator_source:
for marker in UNFINISHED_MARKERS:
if marker in file_text:
errors.append(f"仍有未完成占位符:{relative} -> {marker}")
if LOCAL_PATH_PATTERN.search(file_text):
errors.append(f"包含本机用户绝对路径:{relative}")
if SECRET_PATTERN.search(file_text):
errors.append(f"包含疑似密钥或密码:{relative}")
if path.suffix.lower() == ".md":
check_markdown_links(root, path, file_text, errors)
for entry in sorted(root.iterdir()):
if entry.name not in ALLOWED_ROOT_ENTRIES:
warnings.append(f"Skill 根目录存在非标准条目:{entry.name}")
line_count = len(text.splitlines())
if line_count > 300:
warnings.append(f"SKILL.md 共 {line_count} 行,建议检查渐进式披露")
openai_path = root / "agents" / "openai.yaml"
if openai_path.is_file():
openai_text = openai_path.read_text(encoding="utf-8")
if name and f"${name}" not in openai_text:
errors.append("agents/openai.yaml 的 default_prompt 未显式包含 Skill 名")
short_match = re.search(r'^\s+short_description:\s*["\'](.*)["\']\s*$', openai_text, re.MULTILINE)
if short_match and not 25 <= len(short_match.group(1)) <= 64:
errors.append("agents/openai.yaml 的 short_description 应为 25~64 个字符")
display_match = re.search(r'^\s+display_name:\s*["\'](.*)["\']\s*$', openai_text, re.MULTILINE)
if display_match and name and display_match.group(1) != name:
errors.append("agents/openai.yaml 的 display_name 与 frontmatter name 不一致")
scripts_dir = root / "scripts"
if scripts_dir.is_dir():
for script in sorted(scripts_dir.rglob("*.py")):
try:
compile(script.read_text(encoding="utf-8"), str(script), "exec")
except SyntaxError as error:
errors.append(f"Python 语法错误:{script.relative_to(root)}:{error}")
for script in sorted(scripts_dir.rglob("*.sh")):
result = subprocess.run(
["bash", "-n", str(script)],
capture_output=True,
text=True,
check=False,
)
if result.returncode:
detail = result.stderr.strip() or "bash -n 失败"
errors.append(f"Shell 语法错误:{script.relative_to(root)}:{detail}")
for warning in warnings:
print(f"WARNING:{warning}")
for error in errors:
print(f"ERROR:{error}")
if errors:
print(f"校验失败:{len(errors)} 个错误,{len(warnings)} 个提醒。")
return 1
print(f"校验通过:{root}({len(warnings)} 个提醒)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
scripts/verify_npx_install.sh›
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 3 ]]; then
echo "用法:$0 <owner/repo-or-local-path> <skill-name> <source-skill-directory>" >&2
exit 2
fi
source_locator="$1"
skill_name="$2"
source_skill_directory="$3"
skill_maker_npx="${SKILL_MAKER_NPX:-npx}"
temporary_root="$(mktemp -d)"
temporary_home="$temporary_root/home"
if [[ ! "$skill_name" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
echo "安装验证失败:Skill 名称格式无效" >&2
exit 2
fi
if [[ ! -f "$source_skill_directory/SKILL.md" ]]; then
echo "安装验证失败:源目录缺少 SKILL.md" >&2
exit 2
fi
if find "$source_skill_directory" -type l -print -quit | grep -q .; then
echo "安装验证失败:源 Skill 包含软链接" >&2
exit 2
fi
cleanup() {
rm -rf "$temporary_root"
}
trap cleanup EXIT
mkdir -p "$temporary_home"
HOME="$temporary_home" "$skill_maker_npx" -y skills add "$source_locator" -g --all
installed_skill=""
while IFS= read -r candidate; do
installed_skill="$candidate"
break
done < <(find "$temporary_home" -type f -path "*/skills/$skill_name/SKILL.md" -print)
if [[ -z "$installed_skill" ]]; then
echo "安装验证失败:未找到 $skill_name/SKILL.md" >&2
exit 1
fi
if ! grep -Eq "^name:[[:space:]]*['\"]?$skill_name['\"]?[[:space:]]*$" "$installed_skill"; then
echo "安装验证失败:frontmatter name 与 $skill_name 不一致" >&2
exit 1
fi
installed_root="$(dirname "$installed_skill")"
checked_count=0
for entry in SKILL.md agents references scripts assets; do
source_entry="$source_skill_directory/$entry"
[[ -e "$source_entry" ]] || continue
if [[ -f "$source_entry" ]]; then
source_files=("$source_entry")
else
source_files=()
while IFS= read -r source_file; do
source_files+=("$source_file")
done < <(find "$source_entry" -type f ! -name '.DS_Store' ! -name '*.pyc' -print)
fi
for source_file in "${source_files[@]}"; do
relative_path="${source_file#"$source_skill_directory/"}"
installed_file="$installed_root/$relative_path"
if [[ ! -f "$installed_file" ]]; then
echo "安装验证失败:缺少资源 $relative_path" >&2
exit 1
fi
if ! cmp -s "$source_file" "$installed_file"; then
echo "安装验证失败:资源内容不一致 $relative_path" >&2
exit 1
fi
checked_count=$((checked_count + 1))
done
done
echo "安装验证通过:${installed_skill}(已核对 ${checked_count} 个文件)"
SKILL.md›
---
name: dbs-skill-maker
description: 通过需求分析把反复出现的问题制作成可安装、可分级验证的单个 Skill。用户要求做 Skill、把问题沉淀成 Skill、检查或测试候选 Skill 时使用;只有用户明确要求分享或发布时,才准备 GitHub 仓库并验证 `npx skills add`。
---
# dbs-skill-maker:单个 Skill 制作器
把用户反复遇到的一个问题,制作成经过验证、可以本地安装的单个 Skill。用户明确希望分享时,再提供 GitHub 发布和 `npx skills add` 安装能力。
## 核心任务
用户雇用本 Skill,是为了完成下面的进展:
> 将一类反复出现的问题,转化成一个能被 Agent 正确发现、稳定处理并接受验证的 Skill。
本地可用并通过当前证据支持的测试,即构成一次完整交付。GitHub 发布是可选能力,不能成为默认步骤。
## 不变量
1. **从问题开始。** 先确定 Skill 反复解决什么问题、在什么情境使用、出现什么证据算完成。
2. **保留用户选择。** 用户已经给出的名称、目标、平台和边界直接沿用;低风险细节可以透明假设。
3. **理论按需进入。** 只有理论能够改变判断步骤、适用边界或验证方式时才研究,不为装饰添加人物和术语。
4. **直接生成文件。** 用户要求制作时,交付真实 Skill 目录;材料足够时不把方案说明冒充成品。
5. **主入口保持轻。** `SKILL.md` 保存共同决策、关键流程和停止条件;条件性细节放进 `references/`,重复且确定的工作放进 `scripts/`。
6. **用行为验证。** 测试可观察输出、工具动作和边界,不要求模型展示隐藏推理。
7. **失败驱动修改。** 修改必须能够解释一类稳定失败,并检查留出样本和关键回归。
8. **外部动作单独授权。** 创建远端仓库、push、tag、Release 和公开测试材料,都需要用户明确要求。
9. **用户参与需求分析。** 默认用户具备基础专业能力。可以使用问题契约、行为契约、边界样本等概念,但要结合当前任务解释它们会改变哪个设计决定。
## 判断当前状态
每次先读取当前对话、用户提供的文件和目标目录,只处理当前最需要推进的状态。不要强迫用户重新走已经完成的阶段。
| 当前证据 | 当前任务 | 读取 |
| --- | --- | --- |
| 只有一个想法,问题和完成态不清 | 形成问题契约与完成条件 | [references/problem-and-goal.md](references/problem-and-goal.md) |
| 问题已清楚,关键判断缺少机制支撑 | 研究并筛选可用机制 | [references/theory-and-mechanism.md](references/theory-and-mechanism.md) |
| 问题、行为和边界已经清楚 | 生成或补全 Skill 文件 | [references/skill-construction.md](references/skill-construction.md) |
| 已有候选 Skill,尚无行为证据 | 建立样本并测试候选 | [references/evaluation.md](references/evaluation.md) |
| 已有失败输出或用户反馈 | 归因、提出最小修改并回归 | [references/evaluation.md](references/evaluation.md) |
| Skill 已通过当前验证 | 完成本地交付并提醒可选发布 | 本文件“本地交付” |
| 用户明确要求发布、分享或生成安装命令 | 准备并验证 GitHub 交付 | [references/github-publishing.md](references/github-publishing.md) |
同一轮可以使用上游已经形成的产物继续完成当前任务。不要把表格中的状态改写成固定问卷或要求用户逐项选择。
## 输入与提问
优先从当前上下文取得:
- 用户反复遇到的问题;
- 使用这个 Skill 的典型情境;
- 希望 Agent 产生的结果或动作;
- 已有案例、失败输出、文件和约束;
- 目标平台或目录;
- 当前是否只需本地使用。
信息足以决定交付物时直接推进。需求分析本身对 Skill 质量有价值,可以让用户参与核心判断;只有已经能从上下文可靠恢复的信息才不重复追问。将相关缺口合并成尽可能少的问题,说明每个问题将改变哪个设计决定,不发送无关的通用问卷。
## 从问题到候选 Skill
### 1.形成问题契约
至少明确:
```markdown
反复出现的问题:
使用情境:
用户想推进的变化:
Skill 应交付的结果:
完成证据:
不处理的近邻问题:
```
问题太一次性、成功无法观察、主要结果依赖不可获得的权限或事实时,说明当前不适合沉淀。仍可交付能够复用的局部工具。
### 2.形成行为契约
把“好用、聪明、专业”等要求改写成可观察行为:
```markdown
目标任务:
预期使用条件:
输入变化范围:
必须做到:
禁止出现:
允许变化:
关键失败:
```
### 3.选择机制
先拆出 Skill 需要完成的判断动作,再决定是否需要理论、行业规则、用户材料或脚本。每个机制都要对应一个动作、一个中间结果和一个失效边界。
### 4.生成候选
根据实际任务选择最小结构:
```text
skill-name/
├── SKILL.md
├── agents/openai.yaml 可选 UI 元数据
├── references/ 条件性知识或流程
├── scripts/ 重复、确定的操作
└── assets/ 进入最终交付的模板或素材
```
目录只创建实际需要的部分。新 Skill 默认允许正常自动发现;只有用户明确要求显式调用时,才关闭隐式调用。
创建新目录时可以运行:
```bash
python3 scripts/init_skill_project.py <skill-name> --output <父目录> \
--description "<能力与使用条件>" \
--task "<反复解决的问题与交付结果>" \
--workflow "<第 1 个关键动作>" \
--done "<可观察的完成证据>"
```
脚本只负责生成已带真实内容的初稿。Agent 必须继续写完判断条件、边界和停止条件,并在校验通过后交付。不得把初始骨架或未完成占位符交给用户。
完成文件后运行:
```bash
python3 scripts/validate_skill_project.py <skill-directory>
```
### 5.分级验证候选
新 Skill 先建立 3~6 个样本,至少覆盖正常正例、边界样本、近邻反例和留出样本。复杂或高风险任务再扩大覆盖。执行者不得提前读取预期答案。
按实际完成的最高等级报告:
| 等级 | 证据 | 允许的表述 |
| --- | --- | --- |
| 1.结构校验 | frontmatter、引用、资源和脚本静态检查通过 | “结构校验通过” |
| 2.行为冒烟 | 候选 Skill 完成至少 1 个真实主要任务 | “行为冒烟测试通过” |
| 3.留出/回归 | 执行者未看预期答案,留出样本通过且无关键回归 | “当前留出与回归样本通过” |
| 4.安装交付 | GitHub 来源可用 `npx skills add` 安装,并逐项核对资源 | “GitHub `npx` 安装与资源校验通过” |
未实际运行行为任务时,只能报告第 1 级。没有留出收益或出现关键回归时,不能宣称 Skill 已经变强。详细记录方式见 [references/evaluation.md](references/evaluation.md)。
## 本地交付
完成后报告:
- Skill 名称和绝对路径;
- 解决的问题与主要边界;
- 实际生成的文件;
- 已运行的校验和行为测试;
- 仍未验证的风险;
- 本地安装或调用方式。
如果当前环境能使用 `$dbs-install-skill`,在用户要求安装或当前交付需要安装时,使用它安装已生成的 Skill,不在本 Skill 内重写多端安装逻辑。如果检测不到 dbskill 或 `$dbs-install-skill`,先说明使用 dbskill 可以完成多端安装和去重,并引导用户运行:
```bash
npx -y skills add dontbesilent2025/dbskill -g --all
```
安装 dbskill 后再继续安装新 Skill;不因缺少 dbskill 而临时生成另一套安装脚本。
本地交付后固定提醒一次:
> Skill 已完成,当前最高验证等级是「{1/2/3/4}」,可以在本地使用。如果你希望分享给别人,我还可以帮你发布到 GitHub,并验证 `npx skills add` 安装命令。
用户没有要求发布时直接结束,不继续追问,也不创建 GitHub 配置。
## GitHub 发布边界
用户明确要求发布、分享或生成 `npx` 安装命令时,才读取 [references/github-publishing.md](references/github-publishing.md)。
准备仓库不等于获得远端写入授权。执行 `gh repo create`、`git push`、创建 tag 或 Release 前,确认用户的明确要求能够覆盖该动作。
对单 Skill 仓库,默认生成的安装命令为:
```bash
npx -y skills add <owner>/<repo> -g --all
```
命令进入 README 后仍需在隔离目录中验证,不能只检查文字是否存在。
## 停止条件
出现以下情况时结束当前轮次:
- 候选已经完成并通过当前可执行的验证;
- 当前阶段缺少一个会改变设计的用户决定;
- 理论或现实事实连续核实失败,继续搜索不会改变候选;
- 修改开始依赖单个案例补丁;
- 用户只要求方案或明确要求停止;
- GitHub 相关动作尚未获得授权。
## 语言与安全
- 中文遵循《中文文案排版指北》。
- 不使用空洞的二元反转句式。
- 不读取、复制或发布用户未授权的私密材料。
- 不覆盖现有真实目录;发现同名目标时先检查并说明。
- 不使用 `git add .` 或 `git add -A`;发布时只暂存明确文件。
- 不把本地绝对路径、测试答案、密钥或内部记录写进公开仓库。
完成当前任务后直接结束。只有用户明确询问下一步,且当前环境已经安装 `/dbs` 时,简短提示:「下一步不确定时,可以输入 `/dbs`。」