- SKILL.md 双工作流(创建/迭代)+ 两道门(验料/通用化)+ 三档位 - 7 篇方法论 references + 3 个纯标准库脚本(scaffold/validate/trigger_eval) - 自身触发用例 16 条 + 先例研究/创建/迭代证据报告 - 方法论吸收自 qiaomu-meta-skill 与 luban-skill,取舍见 reports/prior-art.md
127 lines
4.3 KiB
Python
127 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
||
"""LeMetaSkill 起草脚手架:按档位生成"出生即合规"的 skill 骨架。
|
||
|
||
档位与产出:
|
||
- draft 仅 SKILL.md(来自 assets/SKILL.template.md)
|
||
- shared + evals/trigger_cases.json 用例模板 + README.md(三节起步)
|
||
- rigorous + reports/ 证据目录约定
|
||
|
||
用法: python3 scaffold.py <name> "<一句话职责>" [--tier draft|shared|rigorous] [--target 目录]
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
TIERS = ("draft", "shared", "rigorous")
|
||
|
||
README_TEMPLATE = """# {name}
|
||
|
||
> {tagline}
|
||
|
||
**状态**:{tier} 档,个人/项目内使用。
|
||
|
||
## 它解决什么问题
|
||
|
||
【TODO:2-3 句,第二人称说清痛点和交付物】
|
||
|
||
## 触发方式
|
||
|
||
- 【TODO:用户真实会说的原话 3-8 条】
|
||
|
||
## 安全边界
|
||
|
||
【TODO:不会做什么、什么时候停下来问用户】
|
||
"""
|
||
|
||
REPORTS_README = """# reports/ —— 运行证据
|
||
|
||
存放本 skill 的可复查证据,约定:
|
||
|
||
- `trigger-eval.json` trigger_eval.py 的输出(含手工补记的 live_turns 实测段)
|
||
- `creation-handoff.md` 创建交付报告(rigorous 档)
|
||
- `iteration-<日期>.md` 每轮迭代的改动报告与回炉清单
|
||
|
||
诚实条款:验证不了的写「证据缺失」,不编造;计划中的工作不是证据。
|
||
"""
|
||
|
||
CASES_TEMPLATE = """{
|
||
"_说明": "填入用户真实会说的话:应触发/不应触发/边界各若干条。family 标注场景族,便于回归分析。",
|
||
"recommended_threshold": 0.3,
|
||
"negative_patterns": ["不要做成 skill", "只要总结", "翻译一下"],
|
||
"should_trigger": [
|
||
{"text": "【TODO:用户原话】", "family": "cn_case_1"}
|
||
],
|
||
"should_not_trigger": [
|
||
{"text": "【TODO:相邻但不该触发的原话】", "family": "near_task"}
|
||
],
|
||
"near_neighbor": []
|
||
}
|
||
"""
|
||
|
||
|
||
def main() -> int:
|
||
ap = argparse.ArgumentParser(description="生成 skill 骨架")
|
||
ap.add_argument("name")
|
||
ap.add_argument("tagline")
|
||
ap.add_argument("--tier", choices=TIERS, default="draft")
|
||
ap.add_argument("--target", type=Path, default=None)
|
||
args = ap.parse_args()
|
||
|
||
name = args.name.strip().lower()
|
||
if not re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", name):
|
||
print(f"✘ name 必须是 kebab-case(小写字母数字+连字符): {name!r}")
|
||
return 1
|
||
|
||
target = (args.target or Path(".agents/skills") / name).resolve()
|
||
if target.exists() and any(target.iterdir()):
|
||
print(f"✘ 目标目录已存在且非空: {target}(不覆盖,确认后手动清理)")
|
||
return 1
|
||
target.mkdir(parents=True)
|
||
|
||
# 模板:优先读 assets/SKILL.template.md,缺失时用内置兜底
|
||
template_path = Path(__file__).resolve().parent.parent / "assets" / "SKILL.template.md"
|
||
if template_path.is_file():
|
||
template = template_path.read_text(encoding="utf-8")
|
||
else:
|
||
template = (
|
||
"---\nname: {name}\ndescription: |\n {tagline}\n 【TODO:做什么/何时触发,2-3 句 + 触发原话】\n"
|
||
" 不要用于:【TODO:负触发,至少 2 条】。\n---\n\n# {name}\n\n【TODO:工作流正文】\n"
|
||
)
|
||
(target / "SKILL.md").write_text(
|
||
template.replace("{{name}}", name).replace("{{tagline}}", args.tagline),
|
||
encoding="utf-8",
|
||
)
|
||
print(f"✔ {target / 'SKILL.md'}")
|
||
|
||
if args.tier in ("shared", "rigorous"):
|
||
cases_dir = target / "evals"
|
||
cases_dir.mkdir(exist_ok=True)
|
||
(cases_dir / "trigger_cases.json").write_text(CASES_TEMPLATE, encoding="utf-8")
|
||
print(f"✔ {cases_dir / 'trigger_cases.json'}(待填真实原话)")
|
||
(target / "README.md").write_text(
|
||
README_TEMPLATE.format(name=name, tagline=args.tagline, tier=args.tier), encoding="utf-8"
|
||
)
|
||
print(f"✔ {target / 'README.md'}")
|
||
|
||
if args.tier == "rigorous":
|
||
reports = target / "reports"
|
||
reports.mkdir(exist_ok=True)
|
||
(reports / "README.md").write_text(REPORTS_README, encoding="utf-8")
|
||
print(f"✔ {reports / 'README.md'}")
|
||
|
||
print(
|
||
f"\n下一步:\n"
|
||
f"1. 填 TODO(grep -rn 'TODO' {target})——先写好 description 再写正文;\n"
|
||
f"2. python3 scripts/validate_skill.py {target};\n"
|
||
f"3. 填好用例后 python3 scripts/trigger_eval.py {target},再做真实对话实测。"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|