SKILL DETAIL
dbs-skill-maker
dontbesilent2025/dbskill/dbs-skill-maker
dbs-skill-maker is a tool for creating single Skills by analyzing requirements and transforming recurring problems into installable, verifiable Skills. It emphasizes starting from the problem, preserving user choices, introducing theory on demand, generating files directly, keeping the main entry lightweight, validating through behavior, driving modifications by failures, authorizing external actions separately, and involving users in requirement analysis. The tool provides a complete workflow including forming problem contracts, behavior contracts, selecting mechanisms, generating candidates, graded validation, local delivery, and optional GitHub publishing. It supports four validation levels: structural validation, behavioral smoke testing, held-out/regression testing, and installation delivery, ensuring the quality and usability of the Skill.
Installation
npx skills add https://github.com/dontbesilent2025/dbskill --skill dbs-skill-maker
技能檔案
SKILL.md
最近同步 · 2026年10月18日
agents/openai.yaml›
interface:
display_name: "dbs-skill-maker(Skill 制作)"
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
ASCII_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 is_cjk(character: str) -> bool:
codepoint = ord(character)
return (
0x3400 <= codepoint <= 0x4DBF
or 0x4E00 <= codepoint <= 0x9FFF
or 0xF900 <= codepoint <= 0xFAFF
)
def valid_skill_name(name: str) -> bool:
if not 1 <= len(name) < 64:
return False
if ASCII_NAME_PATTERN.fullmatch(name):
return True
if not any(is_cjk(character) for character in name):
return False
return all(
character.isalnum() or character in "-_::()()"
for character in name
)
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 valid_skill_name(name):
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`;发布时只暂存明确文件。
- 不把本地绝对路径、测试答案、密钥或内部记录写进公开仓库。
完成当前任务后直接结束。用户的请求超出本 Skill 的范围、任务完成后用户的材料里出现明确的后续需求,或用户询问下一步时,用一句话说明还需要哪种能力,不点名其他 Skill;当前环境已经安装 `/dbs` 时,补一句「可以输入 `/dbs`,由它匹配对应的 Skill」。