LeMetaSkill v1.1.0:跨 runtime(ZCode/Claude Code/Codex)的 Agent Skill 创建与迭代工坊
- SKILL.md 双工作流(创建/迭代)+ 两道门(验料/通用化)+ 三档位 - 7 篇方法论 references + 3 个纯标准库脚本(scaffold/validate/trigger_eval) - 自身触发用例 16 条 + 先例研究/创建/迭代证据报告 - 方法论吸收自 qiaomu-meta-skill 与 luban-skill,取舍见 reports/prior-art.md
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""LeMetaSkill 结构体检:检查一个 skill 目录是否满足基本契约。
|
||||
|
||||
只依赖标准库。检查项(FAIL 阻断 / WARN 提醒):
|
||||
- SKILL.md 存在、frontmatter 可解析 [FAIL]
|
||||
- name 存在、kebab-case、1-64 字符、与目录名一致 [FAIL]
|
||||
- description 存在且够长(>=30 字符) [WARN]
|
||||
- description 含负触发信号("不要用于"类表述) [WARN]
|
||||
- 正文非空、<=500 行 [WARN]
|
||||
- 正文中的本地 .md 链接可达(无悬空引用) [FAIL]
|
||||
- evals/*.json 可解析 [FAIL]
|
||||
- 无密钥/私有绝对路径 [WARN]
|
||||
- 顶层目录都是已知用途 [WARN]
|
||||
|
||||
用法: python3 validate_skill.py <skill目录>
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
KNOWN_DIRS = {"references", "scripts", "evals", "assets", "reports", "examples", "tests"}
|
||||
MAX_BODY_LINES = 500
|
||||
SECRET_PATTERNS = [
|
||||
r"AKIA[0-9A-Z]{16}",
|
||||
r"sk-[A-Za-z0-9]{20,}",
|
||||
r"(?:api[_-]?key|token|secret|password)\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{16,}",
|
||||
r"ghp_[A-Za-z0-9]{36}",
|
||||
]
|
||||
PRIVATE_PATH = "/Users" + "/[A-Za-z0-9_.\\-]+/" # 拼接写法:避免扫描器匹配到自身源码
|
||||
NEGATIVE_HINT = re.compile(r"不要|不该|不用于|排除|exclude|not for|don'?t use|do not use", re.I)
|
||||
MD_LINK = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
|
||||
|
||||
|
||||
def parse_frontmatter(text: str) -> tuple[dict[str, str], int]:
|
||||
"""解析简易 YAML frontmatter(key: value 与 key: | 块),返回 (字段, 正文起始行号)。"""
|
||||
lines = text.splitlines()
|
||||
if not lines or lines[0].strip() != "---":
|
||||
return {}, 0
|
||||
fields: dict[str, str] = {}
|
||||
key = ""
|
||||
for i in range(1, min(len(lines), 60)):
|
||||
line = lines[i]
|
||||
if line.strip() == "---":
|
||||
return fields, i + 1
|
||||
m = re.match(r"^([A-Za-z_][\w-]*):\s*(.*)$", line)
|
||||
if m:
|
||||
key = m.group(1)
|
||||
fields[key] = m.group(2).strip().strip("'\"")
|
||||
elif line.startswith((" ", "\t")) and key:
|
||||
fields[key] = (fields[key] + " " + line.strip().strip("'\"")).strip()
|
||||
return {}, 0 # 没有闭合的 ---
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="skill 结构体检")
|
||||
ap.add_argument("skill_dir", type=Path)
|
||||
args = ap.parse_args()
|
||||
root: Path = args.skill_dir.resolve()
|
||||
if not root.is_dir():
|
||||
print(f"FAIL 目录不存在: {root}")
|
||||
return 1
|
||||
|
||||
n_pass = n_warn = n_fail = 0
|
||||
|
||||
def ok(msg: str) -> None:
|
||||
nonlocal n_pass
|
||||
n_pass += 1
|
||||
print(f"PASS {msg}")
|
||||
|
||||
def warn(msg: str) -> None:
|
||||
nonlocal n_warn
|
||||
n_warn += 1
|
||||
print(f"WARN {msg}")
|
||||
|
||||
def fail(msg: str) -> None:
|
||||
nonlocal n_fail
|
||||
n_fail += 1
|
||||
print(f"FAIL {msg}")
|
||||
|
||||
skill_md = root / "SKILL.md"
|
||||
if not skill_md.is_file():
|
||||
fail("SKILL.md 不存在")
|
||||
print(f"\n--- Summary --- PASS:{n_pass} WARN:{n_warn} FAIL:{n_fail}")
|
||||
return 1
|
||||
ok("SKILL.md 存在")
|
||||
|
||||
text = skill_md.read_text(encoding="utf-8")
|
||||
fields, body_start = parse_frontmatter(text)
|
||||
if not fields:
|
||||
fail("frontmatter 缺失或未闭合(需要 --- 包裹的头)")
|
||||
else:
|
||||
ok("frontmatter 可解析")
|
||||
|
||||
name = fields.get("name", "")
|
||||
if not name:
|
||||
fail("缺少 name 字段")
|
||||
else:
|
||||
if not re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", name):
|
||||
fail(f"name 不是 kebab-case: {name!r}")
|
||||
elif not 1 <= len(name) <= 64:
|
||||
fail(f"name 长度超出 1-64: {len(name)}")
|
||||
else:
|
||||
ok(f"name 合法: {name}")
|
||||
if root.name != name:
|
||||
fail(f"name 与目录名不一致: {name!r} vs {root.name!r}")
|
||||
else:
|
||||
ok("name 与目录名一致")
|
||||
|
||||
desc = fields.get("description", "")
|
||||
if not desc:
|
||||
fail("缺少 description 字段(触发信号,必填)")
|
||||
else:
|
||||
if len(desc) < 30:
|
||||
warn(f"description 太短({len(desc)} 字符),盖不住触发场景")
|
||||
else:
|
||||
ok(f"description 长度充分({len(desc)} 字符)")
|
||||
if NEGATIVE_HINT.search(desc):
|
||||
ok("description 含负触发信号")
|
||||
else:
|
||||
warn("description 缺负触发(\"不要用于…\"),边界请求会犹豫")
|
||||
|
||||
body = "\n".join(text.splitlines()[body_start:]).strip()
|
||||
if not body:
|
||||
fail("正文为空")
|
||||
else:
|
||||
n_lines = len(body.splitlines())
|
||||
if n_lines > MAX_BODY_LINES:
|
||||
warn(f"正文 {n_lines} 行,超过 {MAX_BODY_LINES} 行预算——考虑拆进 references/")
|
||||
else:
|
||||
ok(f"正文 {n_lines} 行,在预算内")
|
||||
|
||||
# 悬空引用
|
||||
dangling = []
|
||||
for target in MD_LINK.findall(body):
|
||||
if target.startswith(("http://", "https://", "mailto:", "#")):
|
||||
continue
|
||||
if not (root / target).exists():
|
||||
dangling.append(target)
|
||||
if dangling:
|
||||
fail(f"正文引用的本地文件不存在: {', '.join(sorted(set(dangling)))}")
|
||||
elif MD_LINK.search(body):
|
||||
ok("正文本地链接全部可达")
|
||||
|
||||
# evals JSON
|
||||
evals_dir = root / "evals"
|
||||
if evals_dir.is_dir():
|
||||
for j in sorted(evals_dir.glob("*.json")):
|
||||
try:
|
||||
json.loads(j.read_text(encoding="utf-8"))
|
||||
ok(f"{j.relative_to(root)} 可解析")
|
||||
except json.JSONDecodeError as e:
|
||||
fail(f"{j.relative_to(root)} JSON 解析失败: {e}")
|
||||
|
||||
# 密钥与私有路径(只读文本类文件)
|
||||
for p in sorted(root.rglob("*")):
|
||||
if not p.is_file() or p.suffix not in {".md", ".json", ".py", ".sh", ".txt", ".yaml", ".yml"}:
|
||||
continue
|
||||
try:
|
||||
content = p.read_text(encoding="utf-8")
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
continue
|
||||
rel = p.relative_to(root)
|
||||
for pat in SECRET_PATTERNS:
|
||||
if re.search(pat, content):
|
||||
warn(f"{rel} 疑似包含密钥/令牌(模式 {pat})")
|
||||
if rel.name != "SKILL.md" and re.search(PRIVATE_PATH, content):
|
||||
warn(f"{rel} 含私有绝对路径(/Users/…)")
|
||||
|
||||
# 顶层结构
|
||||
unknown = [d.name for d in root.iterdir() if d.is_dir() and d.name not in KNOWN_DIRS]
|
||||
if unknown:
|
||||
warn(f"未知顶层目录(确认其用途,或移除): {', '.join(unknown)}")
|
||||
present = [d.name for d in root.iterdir() if d.is_dir() and d.name in KNOWN_DIRS]
|
||||
if present:
|
||||
ok(f"标准目录: {', '.join(sorted(present))}")
|
||||
|
||||
print(f"\n--- Summary --- PASS:{n_pass} WARN:{n_warn} FAIL:{n_fail}")
|
||||
if n_fail:
|
||||
print("结论:未过体检,先修 FAIL 再交付。")
|
||||
return 1
|
||||
if n_warn:
|
||||
print("结论:可交付,WARN 项酌情处理。")
|
||||
else:
|
||||
print("结论:全绿。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user