feat: open source yao-meta-skill
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
TEXT_EXTS = {".md", ".txt", ".yaml", ".yml", ".json", ".py", ".sh", ".js", ".ts"}
|
||||
|
||||
|
||||
def estimate_tokens(text: str) -> int:
|
||||
# Fast heuristic suitable for local gating.
|
||||
return max(1, len(text) // 4)
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
try:
|
||||
return path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return path.read_text(encoding="utf-8", errors="ignore")
|
||||
|
||||
|
||||
def classify(path: Path) -> str:
|
||||
parts = set(path.parts)
|
||||
if path.name == "SKILL.md":
|
||||
return "skill_body"
|
||||
if "references" in parts:
|
||||
return "reference"
|
||||
if "scripts" in parts:
|
||||
return "script"
|
||||
if "assets" in parts:
|
||||
return "asset"
|
||||
if path.suffix in TEXT_EXTS:
|
||||
return "other_text"
|
||||
return "binary_or_other"
|
||||
|
||||
|
||||
def summarize(skill_dir: Path) -> dict:
|
||||
files = []
|
||||
total_tokens = 0
|
||||
initial_tokens = 0
|
||||
for path in sorted(skill_dir.rglob("*")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
kind = classify(path)
|
||||
if kind in {"binary_or_other", "asset"} and path.suffix not in TEXT_EXTS:
|
||||
size = path.stat().st_size
|
||||
files.append({"path": str(path.relative_to(skill_dir)), "kind": kind, "bytes": size})
|
||||
continue
|
||||
text = read_text(path)
|
||||
tokens = estimate_tokens(text)
|
||||
record = {
|
||||
"path": str(path.relative_to(skill_dir)),
|
||||
"kind": kind,
|
||||
"chars": len(text),
|
||||
"estimated_tokens": tokens,
|
||||
}
|
||||
files.append(record)
|
||||
total_tokens += tokens
|
||||
if kind in {"skill_body", "other_text"}:
|
||||
initial_tokens += tokens
|
||||
return {
|
||||
"skill_dir": str(skill_dir),
|
||||
"estimated_initial_load_tokens": initial_tokens,
|
||||
"estimated_total_text_tokens": total_tokens,
|
||||
"warning": initial_tokens > 2000,
|
||||
"files": files,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Estimate context size for a skill package.")
|
||||
parser.add_argument("skill_dir", help="Path to the skill directory")
|
||||
parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = summarize(Path(args.skill_dir).resolve())
|
||||
if args.json:
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
print(f"Skill: {report['skill_dir']}")
|
||||
print(f"Estimated initial-load tokens: {report['estimated_initial_load_tokens']}")
|
||||
print(f"Estimated total text tokens: {report['estimated_total_text_tokens']}")
|
||||
print(f"Initial-load warning (>2000): {'YES' if report['warning'] else 'NO'}")
|
||||
print("")
|
||||
for file in report["files"]:
|
||||
if "estimated_tokens" in file:
|
||||
print(f"{file['kind']:12} {file['estimated_tokens']:6}t {file['path']}")
|
||||
else:
|
||||
print(f"{file['kind']:12} {file['bytes']:6}b {file['path']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def read_simple_yaml(path: Path) -> dict:
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
data: dict = {}
|
||||
stack: list[tuple[int, dict]] = [(0, data)]
|
||||
for raw_line in lines:
|
||||
if not raw_line.strip() or raw_line.lstrip().startswith("#"):
|
||||
continue
|
||||
indent = len(raw_line) - len(raw_line.lstrip(" "))
|
||||
line = raw_line.strip()
|
||||
while len(stack) > 1 and indent <= stack[-1][0]:
|
||||
stack.pop()
|
||||
parent = stack[-1][1]
|
||||
if line.startswith("- "):
|
||||
item = line[2:].strip().strip("'\"")
|
||||
existing = parent.setdefault("__list__", [])
|
||||
existing.append(item)
|
||||
continue
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, value = line.split(":", 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if value == "":
|
||||
child: dict = {}
|
||||
parent[key] = child
|
||||
stack.append((indent, child))
|
||||
else:
|
||||
parent[key] = value.strip("'\"")
|
||||
return data
|
||||
|
||||
|
||||
def read_frontmatter(skill_md: Path) -> dict:
|
||||
text = skill_md.read_text(encoding="utf-8")
|
||||
if not text.startswith("---"):
|
||||
return {}
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return {}
|
||||
data = {}
|
||||
for line in parts[1].splitlines():
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, value = line.split(":", 1)
|
||||
data[key.strip()] = value.strip().strip("'\"")
|
||||
return data
|
||||
|
||||
|
||||
def read_interface(skill_dir: Path) -> dict:
|
||||
path = skill_dir / "agents" / "interface.yaml"
|
||||
if not path.exists():
|
||||
return {}
|
||||
raw = read_simple_yaml(path)
|
||||
compatibility = raw.get("compatibility", {})
|
||||
targets = compatibility.get("adapter_targets", {})
|
||||
if isinstance(targets, dict) and "__list__" in targets:
|
||||
compatibility["adapter_targets"] = targets["__list__"]
|
||||
raw["compatibility"] = compatibility
|
||||
return raw
|
||||
|
||||
|
||||
def build_manifest(skill_dir: Path, platform: str) -> dict:
|
||||
frontmatter = read_frontmatter(skill_dir / "SKILL.md")
|
||||
interface = read_interface(skill_dir).get("interface", {})
|
||||
compatibility = read_interface(skill_dir).get("compatibility", {})
|
||||
return {
|
||||
"name": frontmatter.get("name", skill_dir.name),
|
||||
"description": frontmatter.get("description", ""),
|
||||
"version": frontmatter.get("version", "1.0.0"),
|
||||
"platform": platform,
|
||||
"skill_root": skill_dir.name,
|
||||
"display_name": interface.get("display_name", skill_dir.name),
|
||||
"short_description": interface.get("short_description", ""),
|
||||
"default_prompt": interface.get("default_prompt", ""),
|
||||
"canonical_metadata": "agents/interface.yaml",
|
||||
"adapter_targets": compatibility.get("adapter_targets", []),
|
||||
}
|
||||
|
||||
|
||||
def write_yaml_like(path: Path, payload: dict) -> None:
|
||||
interface = payload.get("interface", {})
|
||||
lines = ["interface:"]
|
||||
for key in ("display_name", "short_description", "default_prompt"):
|
||||
value = interface.get(key, "")
|
||||
lines.append(f' {key}: "{value}"')
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def write_adapter(skill_dir: Path, out_dir: Path, platform: str) -> Path:
|
||||
target_dir = out_dir / "targets" / platform
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
payload = build_manifest(skill_dir, platform)
|
||||
if platform == "openai":
|
||||
meta_dir = target_dir / "agents"
|
||||
meta_dir.mkdir(parents=True, exist_ok=True)
|
||||
write_yaml_like(
|
||||
meta_dir / "openai.yaml",
|
||||
{
|
||||
"interface": {
|
||||
"display_name": payload["display_name"],
|
||||
"short_description": payload["short_description"],
|
||||
"default_prompt": payload["default_prompt"],
|
||||
}
|
||||
},
|
||||
)
|
||||
payload["install_hint"] = f"Use the packaged skill and include targets/openai/agents/openai.yaml when the client expects OpenAI-style interface metadata."
|
||||
elif platform == "claude":
|
||||
notes = target_dir / "README.md"
|
||||
notes.write_text(
|
||||
f"# Claude-Compatible Package\n\nUse `{skill_dir.name}` with its neutral source files. This target does not require vendor metadata by default.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
payload["install_hint"] = f"Use the packaged skill directly; this target relies on SKILL.md and optional neutral metadata."
|
||||
else:
|
||||
payload["install_hint"] = f"Use {skill_dir.name} as an Agent Skills compatible package."
|
||||
path = target_dir / "adapter.json"
|
||||
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def make_zip(skill_dir: Path, out_dir: Path) -> Path:
|
||||
zip_path = out_dir / f"{skill_dir.name}.zip"
|
||||
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
for path in skill_dir.rglob("*"):
|
||||
if path.is_file():
|
||||
zf.write(path, arcname=str(path.relative_to(skill_dir.parent)))
|
||||
return zip_path
|
||||
|
||||
|
||||
def copy_manifest(skill_dir: Path, out_dir: Path) -> Path:
|
||||
manifest_path = out_dir / "manifest.json"
|
||||
manifest_path.write_text(
|
||||
json.dumps(build_manifest(skill_dir, "generic"), ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return manifest_path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Generate lightweight cross-platform packaging artifacts.")
|
||||
parser.add_argument("skill_dir", help="Path to the skill directory")
|
||||
parser.add_argument("--platform", action="append", default=[], help="Target platform: openai, claude, generic")
|
||||
parser.add_argument("--output-dir", default="dist", help="Output directory")
|
||||
parser.add_argument("--zip", action="store_true", help="Create a zip package")
|
||||
args = parser.parse_args()
|
||||
|
||||
skill_dir = Path(args.skill_dir).resolve()
|
||||
out_dir = Path(args.output_dir).resolve()
|
||||
if out_dir.exists():
|
||||
shutil.rmtree(out_dir)
|
||||
out_dir.mkdir(parents=True)
|
||||
|
||||
manifest = copy_manifest(skill_dir, out_dir)
|
||||
generated = [str(manifest)]
|
||||
for platform in (args.platform or ["generic"]):
|
||||
generated.append(str(write_adapter(skill_dir, out_dir, platform)))
|
||||
if args.zip:
|
||||
generated.append(str(make_zip(skill_dir, out_dir)))
|
||||
|
||||
print(json.dumps({"output_dir": str(out_dir), "generated": generated}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
WORD_RE = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9_-]*")
|
||||
|
||||
|
||||
def words(text: str) -> set[str]:
|
||||
return {w.lower() for w in WORD_RE.findall(text)}
|
||||
|
||||
|
||||
def load_cases(path: Path) -> dict:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def extract_description(text: str) -> str:
|
||||
if not text.startswith("---"):
|
||||
return text
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return text
|
||||
frontmatter = parts[1].splitlines()
|
||||
for line in frontmatter:
|
||||
if line.strip().startswith("description:"):
|
||||
return line.split(":", 1)[1].strip().strip("'\"")
|
||||
return text
|
||||
|
||||
|
||||
def score_prompt(description_words: set[str], prompt: str) -> float:
|
||||
prompt_words = words(prompt)
|
||||
if not prompt_words:
|
||||
return 0.0
|
||||
overlap = description_words & prompt_words
|
||||
return len(overlap) / len(prompt_words)
|
||||
|
||||
|
||||
def evaluate(description: str, cases: dict, threshold: float) -> dict:
|
||||
desc_words = words(description)
|
||||
results = {"should_trigger": [], "should_not_trigger": []}
|
||||
fp = 0
|
||||
fn = 0
|
||||
|
||||
for bucket in ("should_trigger", "should_not_trigger"):
|
||||
for prompt in cases.get(bucket, []):
|
||||
score = score_prompt(desc_words, prompt)
|
||||
predicted = score >= threshold
|
||||
expected = bucket == "should_trigger"
|
||||
passed = predicted == expected
|
||||
if not passed and expected:
|
||||
fn += 1
|
||||
if not passed and not expected:
|
||||
fp += 1
|
||||
results[bucket].append(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"score": round(score, 3),
|
||||
"predicted_trigger": predicted,
|
||||
"passed": passed,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"threshold": threshold,
|
||||
"false_positives": fp,
|
||||
"false_negatives": fn,
|
||||
"results": results,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Heuristic trigger quality evaluator.")
|
||||
parser.add_argument("--description", help="Description string to evaluate")
|
||||
parser.add_argument("--description-file", help="Read description text from file")
|
||||
parser.add_argument("--cases", required=True, help="JSON file with should_trigger and should_not_trigger arrays")
|
||||
parser.add_argument("--threshold", type=float, default=0.18, help="Token overlap threshold")
|
||||
args = parser.parse_args()
|
||||
|
||||
description = args.description
|
||||
if args.description_file:
|
||||
description = extract_description(Path(args.description_file).read_text(encoding="utf-8"))
|
||||
if not description:
|
||||
raise SystemExit("Provide --description or --description-file")
|
||||
|
||||
report = evaluate(description, load_cases(Path(args.cases)), args.threshold)
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
if report["false_positives"] > 2:
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user