创建技能骨架
填写技能名称、用途和触发词,点击生成后核对 name 与 description。输出包含 When to use、Steps 和 Do not 小节,可以下载为 SKILL.md。用途应说明要完成的具体任务,步骤则应指向实际文件、脚本或验收方法。
填写示例
名称可以填写 release-check,用途填写检查构建产物与版本信息,触发词填写 release、版本、构建。生成后把通用步骤改成项目的构建命令、产物路径和检查顺序,并按使用的客户端要求整理技能目录。
离线验收:先区分 YAML 能解析和技能可加载
下载后的草稿值得先过一道确定性的检查。前文已记录默认 description 中未引用的 Triggers: 会破坏 YAML;更隐蔽的情况是解析没有报错,实际字段却与作者看到的文字不同。本练习把失败样本、修复方法和固定预期放在同一个文件里,用来检查准备交给宿主的文本。它不连接模型、不安装技能,也不改变本页生成器。
这是一份原创的两字段教学检查器,只接受 name 与 description。它要求首行和结束分隔行均为三个连字符,正文非空,目录名由调用者明确提供;检查重复键、字符串类型、名称、长度及逐字符回读。license 等规范允许的可选字段在本练习中也会被拒绝,这是有意缩小的检查范围,不代表这些字段不合法。
2026-10-14 查阅的 Agent Skills 规范要求 name 与父目录同名,名称为 1—64 字符且不首尾连字符、无连续连字符;description 为 1—1024 字符。这里名称采用更窄的 ASCII 小写字母、数字和单连字符组合。正文非空、仅两个字段、禁止锚点、整份文本最多 32768 个 UTF-8 字节,都是本例的额外策略;不是完整官方校验器,也不判断描述写得是否准确。
完整源码:检查原稿,再序列化已确认的字符串
将下面完整代码保存为 check_frontmatter.py。在已有 Python 与 PyYAML 的独立练习环境运行 python check_frontmatter.py。本次复跑使用环境中已有的 Python 3.12.14、PyYAML 6.0.3;没有声称做过全新安装验收。复现时可在自己的虚拟环境用 python -m pip install PyYAML==6.0.3 准备依赖。脚本只处理内存中的合成字符串并输出结果,不遍历技能目录、不写源文件、不运行 Markdown 正文。
检查器先扫描并拒绝锚点和别名,再用 SafeLoader 解析节点,不先转成字典,因此重复 name 不会在检查前被后一个值覆盖。字段值必须具有字符串节点类型;on 和 123 在本次 PyYAML 环境中会触发非字符串拒绝。draft 函数从人工确认的原始字符串重新序列化,正确处理引号、反斜线、换行、井号及冒号。它不从歧义输入中自动选一个版本。
import json
import re
import sys
import yaml
from yaml.nodes import MappingNode, ScalarNode
from yaml.tokens import AliasToken, AnchorToken
STR = 'tag:yaml.org,2002:str'
MAP = 'tag:yaml.org,2002:map'
class Rejected(ValueError):
pass
def check(text, directory):
# Deliberately narrow, two-field teaching profile, not a full Skills validator.
if len(text.encode('utf-8')) > 32768:
raise Rejected('size')
lines = text.splitlines()
if not lines or lines[0] != '---':
raise Rejected('opening')
try:
end = lines.index('---', 1)
except ValueError:
raise Rejected('closing') from None
header = '\n'.join(lines[1:end])
try:
if any(isinstance(t, (AliasToken, AnchorToken))
for t in yaml.scan(header, Loader=yaml.SafeLoader)):
raise Rejected('anchors')
root = yaml.compose(header, Loader=yaml.SafeLoader)
except yaml.YAMLError:
raise Rejected('yaml') from None
if not isinstance(root, MappingNode) or root.tag != MAP:
raise Rejected('mapping')
values = {}
for key, value in root.value:
if not isinstance(key, ScalarNode) or key.tag != STR:
raise Rejected('key-type')
if key.value in values:
raise Rejected('duplicate')
if not isinstance(value, ScalarNode) or value.tag != STR:
raise Rejected('value-type')
values[key.value] = value.value
if set(values) != {'name', 'description'}:
raise Rejected('fields')
name, description = values['name'], values['description']
# This example selects the portable ASCII subset for names.
if not (1 <= len(name) <= 64 and
re.fullmatch(r'[a-z0-9]+(?:-[a-z0-9]+)*', name)):
raise Rejected('name')
if name != directory:
raise Rejected('directory')
if not description.strip() or len(description) > 1024:
raise Rejected('description')
if not '\n'.join(lines[end + 1:]).strip():
raise Rejected('body')
return values
def quote(value):
return yaml.safe_dump(value, default_style=chr(34), allow_unicode=True, width=4096).strip()
def draft(name, description):
# Serialize reviewed strings; never repair an ambiguous duplicate by guessing.
return ('---\nname: ' + quote(name) +
'\ndescription: ' + quote(description) +
'\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n')
def observed(text, directory='review-release'):
try:
check(text, directory)
return 'ok'
except Rejected as error:
return str(error)
def main():
good = draft('review-release', '核对发布说明。Use when asked to review release facts.')
default_bad = ('---\nname: deploy-workers\ndescription: Build and deploy a '
'Cloudflare Worker after tests pass. Triggers: deploy, wrangler, workers.'
'\n---\n# Steps\nReview first.\n')
cases = [
('valid', good, 'review-release', 'ok'),
('default-colon', default_bad, 'deploy-workers', 'yaml'),
('duplicate', good.replace('description:', 'name: other\ndescription:'), 'review-release', 'duplicate'),
('boolean', good.replace('name: "review-release"', 'name: on'), 'review-release', 'value-type'),
('numeric', good.replace('name: "review-release"', 'name: 123'), 'review-release', 'value-type'),
('wrong-directory', good, 'other-directory', 'directory'),
('uppercase', draft('Review-release', 'check'), 'Review-release', 'name'),
('double-hyphen', draft('review--release', 'check'), 'review--release', 'name'),
('long-name', draft('a' * 65, 'check'), 'a' * 65, 'name'),
('empty-description', draft('review-release', ' '), 'review-release', 'description'),
('long-description', draft('review-release', 'x' * 1025), 'review-release', 'description'),
('opening-missing', good[4:], 'review-release', 'opening'),
('closing-missing', good.replace('\n---\n', '\n', 1), 'review-release', 'closing'),
('no-body', good.split('\n---\n')[0] + '\n---\n', 'review-release', 'body'),
('extra-field', good.replace('description:', 'license: MIT\ndescription:'), 'review-release', 'fields'),
('anchor', good.replace('description:', 'description: &label'), 'review-release', 'anchors'),
('oversized', good + 'x' * 32768, 'review-release', 'size'),
]
for label, text, directory, expected in cases:
actual = observed(text, directory)
if actual != expected:
raise AssertionError((label, expected, actual))
print(label + ': ' + actual)
tricky = '核对 "引号"、路径 C:\\notes、# 标签;Triggers: release\n不要发布。'
recovered = check(draft('review-release', tricky), 'review-release')
if recovered != {'name': 'review-release', 'description': tricky}:
raise AssertionError('roundtrip mismatch')
print('repair-roundtrip: exact')
lax = yaml.safe_load('name: first\nname: second\n')
if lax != {'name': 'second'}:
raise AssertionError('negative control changed')
print('negative-duplicate: last value silently retained')
cut = yaml.safe_load('description: Review # keep literal\n')
if cut != {'description': 'Review'}:
raise AssertionError('negative comment control changed')
print('negative-comment: valid YAML lost intended suffix')
if check(good, 'review-release')['name'] != 'review-release':
raise AssertionError('final recovery')
print('final-recovery: ok')
print('PASS: 21 fixed checks')
if __name__ == '__main__':
print('Python ' + sys.version.split()[0] + '; PyYAML ' + yaml.__version__)
main()
quote 使用 PyYAML 的双引号标量输出,保留中文与 emoji,并由序列化器转义 YAML 特殊字符,width=4096 避免本例短描述被自动折行。不能简单改用 JSON 的 ensure_ascii=True:PyYAML 会把 emoji 的 JSON 代理对读成两个代理字符。补充测试覆盖 emoji、三种 Unicode 分隔字符、控制字符、U+FFFE 与长字符串,逐字符要求恢复原值。
固定答案与实际运行:失败要具体,恢复要保真
下面是 2026-10-14 的真实本地运行输出,退出码为 0。输入全部是教学样本;default-colon 逐字采用本页已经记录的默认 frontmatter 内容,复制到本地复现,不是新一轮浏览器点击测试。代码中每一项 expected 都是预先写定的答案,遇到任何不符会抛出 AssertionError;结果不是靠数一数输出行来判定成功。
Python 3.12.14; PyYAML 6.0.3 valid: ok default-colon: yaml duplicate: duplicate boolean: value-type numeric: value-type wrong-directory: directory uppercase: name double-hyphen: name long-name: name empty-description: description long-description: description opening-missing: opening closing-missing: closing no-body: body extra-field: fields anchor: anchors oversized: size repair-roundtrip: exact negative-duplicate: last value silently retained negative-comment: valid YAML lost intended suffix final-recovery: ok PASS: 21 fixed checks
17 个表内样本覆盖正常输入、默认冒号错误、重复键、布尔与数字值、目录不符、名称大小写与连续连字符、字段长度、分隔符、空正文、额外字段、锚点和大小上限。另有四个固定检查:修复后逐字符往返、两个故意宽松的反面例子,以及在全部拒绝样本之后重新接受正常稿。共 21 项。
repair-roundtrip 不是只检查“没有异常”。它要求恢复后的整个字典与原始 name、description 完全相等,其中 description 特意含中文、引号、反斜线路径、井号、冒号加空格及真实换行。把井号后面的文字丢掉,或者把换行变成两个字面字符,都不能通过这个答案。
两个反面例子:安全加载器不等于业务校验器
negative-duplicate 使用 yaml.safe_load 读取两个同名 name。当前测试版本静默保留 second;YAML 语法入口没有替读者决定哪一行才是已确认事实。我们的节点检查在构造字典之前拒绝重复,恢复方式是让作者确认唯一名称并删去多余定义,而不是自动保留第一条或最后一条。
negative-comment 的文本是 description: Review # keep literal。它是可解析的 YAML,但读取结果仅为 Review,井号后的部分被视为注释。如果作者确实想保留那段文字,需要对完整值进行正确引用或序列化。只加一个“解析通过”的绿色标记,发现不了这一类语义丢失。
YAML 1.2.2 的 plain style 说明解释了未引用标量与冒号、井号的歧义。PyYAML 官方文档说明 SafeLoader 限制可构造的对象类型。此处没有把 SafeLoader 宣称为任意不可信 YAML 的安全沙箱;小型离线样本仍须限制输入大小与处理范围,生产入口还需独立的资源隔离和超时设计。
一个重要限制:把这条未引用的 description 放进本例检查器,也会得到 ok,值仍只有 Review。节点类型检查能防止类型变化,不能读取作者意图。test_contract.py 明确固定这一结果,再从作者确认的完整字符串重建头部,要求修复值逐字符相等。PyYAML 的 on 布尔行为是本次固定版本的解析行为,不应当作所有 YAML 1.2 解析器的共同保证。
拿到拒绝结果后怎样继续
- 出现 yaml:回到原始描述,确认完整文字,再使用 draft 的序列化方式生成新的 frontmatter;不要用全局替换把所有冒号删掉,这会改变用途描述。
- 出现 duplicate:请作者明确唯一字段值,再重建头部。不要把两个声明合并成猜测的名称,也不要静默采用最后一个。
- 出现 value-type:检查该字段是不是本来就应该是字符串;确认后引用原文。把 on 改成字符串只能修复类型,不能证明这个名称表达了正确任务。
- 出现 directory:核对实际存放目录与 name。函数接收的 directory 是练习输入,脚本并没有替你检查磁盘上的真实路径。
- 出现 fields 或 anchors:确认是否在使用本例故意不支持的合法能力。需要可选字段时改用或扩展相应校验方案,不要为了让本脚本变绿而删除必要元数据。
- 出现 ok:再人工核对用途、步骤、所需文件、停止条件与授权范围,并在目标宿主单独记录发现、加载及实际执行结果。
本次实际验证包括上述 21 项主脚本固定检查及下文补充回归检查。没有测试本页下载按钮、修改页面控件、检查新输入的浏览器输出、把技能装入 Codex 或其他宿主、运行部署、调用模型,亦未验证全部 YAML 形式或全部 Skills 字段。原有页面的生成器问题仍然存在;这份补充提供的是下载后人工审查与复现路径。
可复现材料:固定输入、完整答案与反向校验
下面的 fixtures.json 展开全部 17 个表内输入,expected.txt 固定 21 项运行输出。检查结果与输入分别保存;test_contract.py 还故意把 default-colon 的答案改成 ok,要求错误答案在普通模式与 python -O 下都退出失败。全部判断使用显式异常,不依赖会被 -O 删除的 assert 语句。
oversized 的 32768 个重复字符以 text_prefix 与 append_repeat 确定性展开,避免把大段填充误当教程内容。
fixtures.json
[
{
"label": "valid",
"text": "---\nname: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "ok"
},
{
"label": "default-colon",
"text": "---\nname: deploy-workers\ndescription: Build and deploy a Cloudflare Worker after tests pass. Triggers: deploy, wrangler, workers.\n---\n# Steps\nReview first.\n",
"directory": "deploy-workers",
"expected": "yaml"
},
{
"label": "duplicate",
"text": "---\nname: \"review-release\"\nname: other\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "duplicate"
},
{
"label": "boolean",
"text": "---\nname: on\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "value-type"
},
{
"label": "numeric",
"text": "---\nname: 123\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "value-type"
},
{
"label": "wrong-directory",
"text": "---\nname: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "other-directory",
"expected": "directory"
},
{
"label": "uppercase",
"text": "---\nname: \"Review-release\"\ndescription: \"check\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "Review-release",
"expected": "name"
},
{
"label": "double-hyphen",
"text": "---\nname: \"review--release\"\ndescription: \"check\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review--release",
"expected": "name"
},
{
"label": "long-name",
"text": "---\nname: \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\ndescription: \"check\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"expected": "name"
},
{
"label": "empty-description",
"text": "---\nname: \"review-release\"\ndescription: \" \"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "description"
},
{
"label": "long-description",
"text": "---\nname: \"review-release\"\ndescription: \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "description"
},
{
"label": "opening-missing",
"text": "name: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "opening"
},
{
"label": "closing-missing",
"text": "---\nname: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "closing"
},
{
"label": "no-body",
"text": "---\nname: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n",
"directory": "review-release",
"expected": "body"
},
{
"label": "extra-field",
"text": "---\nname: \"review-release\"\nlicense: MIT\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "fields"
},
{
"label": "anchor",
"text": "---\nname: \"review-release\"\ndescription: &label \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"directory": "review-release",
"expected": "anchors"
},
{
"label": "oversized",
"directory": "review-release",
"expected": "size",
"text_prefix": "---\nname: \"review-release\"\ndescription: \"核对发布说明。Use when asked to review release facts.\"\n---\n\n# 范围\n只核对用户提供的发布事实,不写文件。\n",
"append_repeat": {
"text": "x",
"count": 32768
}
}
]
test_contract.py
import json
import pathlib
import subprocess
import sys
from check_frontmatter import check, draft, observed
P = pathlib.Path(__file__).resolve().parent
fixtures = json.loads((P / "fixtures.json").read_text())
for item in fixtures:
text = item.get("text")
if text is None:
repeat = item["append_repeat"]
text = item["text_prefix"] + repeat["text"] * repeat["count"]
actual = observed(text, item["directory"])
if actual != item["expected"]:
raise AssertionError((item["label"], item["expected"], actual))
print("fixture-oracle: 17 exact results")
def need_equal(actual, expected, label):
if actual != expected:
raise AssertionError(label)
# The checker cannot recover author intent from a syntactically valid comment.
comment = "---\nname: review-release\ndescription: Review # keep literal\n---\n# Steps\nReview first.\n"
need_equal(check(comment, "review-release")["description"], "Review", "comment truncation")
print("strict-checker-comment: accepts truncated Review")
intended = "Review # keep literal"
need_equal(check(draft("review-release", intended), "review-release")["description"], intended, "comment repair")
print("comment-repair: exact")
for separator in (chr(0x85), chr(0x2028), chr(0x2029), '中文🙂', '🙂' + chr(0x2028) + '中文', chr(0x80), chr(0x7f), chr(0xfffe), '词 🙂 ' * 200):
intended_unicode = 'a' + separator + 'b'
need_equal(check(draft("review-release", intended_unicode), "review-release")["description"], intended_unicode, "Unicode separator repair")
print("unicode: U+0085 U+2028 U+2029 and Chinese/emoji exact")
intended_default = "Build and deploy a Cloudflare Worker after tests pass. Triggers: deploy, wrangler, workers."
need_equal(check(draft("deploy-workers", intended_default), "deploy-workers")["description"], intended_default, "default repair")
print("default-colon-repair: exact")
expected = (P / "expected.txt").read_text()
code = (P / "check_frontmatter.py").read_text()
for flags in ([], ["-O"]):
run = subprocess.run([sys.executable, *flags, str(P / "check_frontmatter.py")], capture_output=True, text=True)
need_equal(run.returncode, 0, "source exit")
need_equal(run.stdout, expected, "source stdout")
# Change a known failing sample's fixed expected verdict, never its input.
mutant = code.replace("'deploy-workers', 'yaml')", "'deploy-workers', 'ok')", 1)
if mutant == code:
raise AssertionError("mutation target missing")
bad = subprocess.run([sys.executable, *flags, "-c", mutant], capture_output=True, text=True)
if bad.returncode == 0 or "AssertionError" not in bad.stderr:
raise AssertionError("false oracle was accepted")
print("source " + ("-O" if flags else "normal") + ": exact stdout; false oracle rejected")
print("PASS: contract, repairs and mutation controls")
requirements.txt
PyYAML==6.0.3
依赖需预先准备。联网安装命令仅是读者复现说明;完成安装后,脚本本身不联网。可在 Python 3.12.14 环境中执行 python -m venv .venv,激活环境后执行 python -m pip install -r requirements.txt,再运行 python check_frontmatter.py、python -O check_frontmatter.py、python test_contract.py 与 python -O test_contract.py。若 Python 或 PyYAML 版本不同,固定输出比较会失败;先记录版本差异,不要直接覆盖 expected.txt。
本例没有使用任何模型 SDK,也没有调用模型 API;“通过”只指这些固定离线样本与回归断言,不包括真实技能发现、加载、模型遵循程度或部署成功。
资料与实验边界
资料核对日期:2026-10-14。Agent Skills 官方格式规范用于对照必需字段与名称约束;PyYAML 官方文档用于节点接口及 SafeLoader 的对象构造边界;PyYAML 6.0.3 发布页用于依赖版本;YAML 1.2.2 plain style用于理解未引用标量中的冒号与井号。这里没有运行 skills-ref 或其他官方验证器,且本例额外拒绝可选字段、锚点等能力,不能把它的拒绝等同于规范不合法。
常见问题
下载后技能会自动启用吗?
下载得到的是 Markdown 文件。需要按客户端的技能安装方式放置文件,再验证触发条件和步骤是否有效。