master-skill 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +22 -0
- package/.codex/INSTALL.md +45 -0
- package/.cursor-plugin/plugin.json +25 -0
- package/.opencode/INSTALL.md +46 -0
- package/ETHICS.md +198 -0
- package/GEMINI.md +2 -0
- package/LICENSE +21 -0
- package/README.md +425 -0
- package/README_EN.md +398 -0
- package/SKILL.md +393 -0
- package/bin/cli.mjs +148 -0
- package/gemini-extension.json +6 -0
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks.json +16 -0
- package/hooks/run-hook.cmd +29 -0
- package/hooks/session-start +47 -0
- package/package.json +74 -0
- package/prebuilt/ajahn-chah/SKILL.md +126 -0
- package/prebuilt/ajahn-chah/meta.json +100 -0
- package/prebuilt/ajahn-chah/references/teaching.md +164 -0
- package/prebuilt/ajahn-chah/references/voice.md +98 -0
- package/prebuilt/ajahn-chah/sources/INDEX.md +24 -0
- package/prebuilt/ajahn-chah/sources/sutta-excerpts.md +128 -0
- package/prebuilt/ajahn-chah/sources/teachings-excerpts.md +204 -0
- package/prebuilt/ajahn-chah/tests/fidelity.jsonl +13 -0
- package/prebuilt/compare/SKILL.md +277 -0
- package/prebuilt/compare/tests/fidelity.jsonl +5 -0
- package/prebuilt/fazang/SKILL.md +132 -0
- package/prebuilt/fazang/meta.json +101 -0
- package/prebuilt/fazang/references/teaching.md +121 -0
- package/prebuilt/fazang/references/voice.md +95 -0
- package/prebuilt/fazang/sources/INDEX.md +22 -0
- package/prebuilt/fazang/sources/jinshizi-excerpts.md +35 -0
- package/prebuilt/fazang/sources/wujiao-zhang-excerpts.md +68 -0
- package/prebuilt/fazang/tests/fidelity.jsonl +10 -0
- package/prebuilt/huineng/SKILL.md +124 -0
- package/prebuilt/huineng/meta.json +91 -0
- package/prebuilt/huineng/references/teaching.md +122 -0
- package/prebuilt/huineng/references/voice.md +96 -0
- package/prebuilt/huineng/sources/INDEX.md +22 -0
- package/prebuilt/huineng/sources/jingang-excerpts.md +47 -0
- package/prebuilt/huineng/sources/tanjing-excerpts.md +81 -0
- package/prebuilt/huineng/tests/fidelity.jsonl +10 -0
- package/prebuilt/kumarajiva/SKILL.md +135 -0
- package/prebuilt/kumarajiva/meta.json +113 -0
- package/prebuilt/kumarajiva/references/teaching.md +131 -0
- package/prebuilt/kumarajiva/references/voice.md +99 -0
- package/prebuilt/kumarajiva/sources/INDEX.md +23 -0
- package/prebuilt/kumarajiva/sources/fahua-excerpts.md +33 -0
- package/prebuilt/kumarajiva/sources/jingang-bore-excerpts.md +46 -0
- package/prebuilt/kumarajiva/sources/zhonglun-excerpts.md +35 -0
- package/prebuilt/kumarajiva/tests/fidelity.jsonl +10 -0
- package/prebuilt/milarepa/SKILL.md +122 -0
- package/prebuilt/milarepa/meta.json +83 -0
- package/prebuilt/milarepa/references/teaching.md +135 -0
- package/prebuilt/milarepa/references/voice.md +99 -0
- package/prebuilt/milarepa/sources/INDEX.md +24 -0
- package/prebuilt/milarepa/sources/grubum-excerpts.md +124 -0
- package/prebuilt/milarepa/sources/namthar-excerpts.md +134 -0
- package/prebuilt/milarepa/tests/fidelity.jsonl +12 -0
- package/prebuilt/ouyi/SKILL.md +130 -0
- package/prebuilt/ouyi/meta.json +108 -0
- package/prebuilt/ouyi/references/teaching.md +118 -0
- package/prebuilt/ouyi/references/voice.md +97 -0
- package/prebuilt/ouyi/sources/INDEX.md +22 -0
- package/prebuilt/ouyi/sources/jiaoguan-gangzong-excerpts.md +34 -0
- package/prebuilt/ouyi/sources/mituo-yaojie-excerpts.md +50 -0
- package/prebuilt/ouyi/tests/fidelity.jsonl +10 -0
- package/prebuilt/xuanzang/SKILL.md +135 -0
- package/prebuilt/xuanzang/meta.json +110 -0
- package/prebuilt/xuanzang/references/teaching.md +162 -0
- package/prebuilt/xuanzang/references/voice.md +99 -0
- package/prebuilt/xuanzang/sources/INDEX.md +22 -0
- package/prebuilt/xuanzang/sources/chengweishi-excerpts.md +67 -0
- package/prebuilt/xuanzang/sources/xinjing-excerpts.md +34 -0
- package/prebuilt/xuanzang/tests/fidelity.jsonl +10 -0
- package/prebuilt/xuyun/SKILL.md +127 -0
- package/prebuilt/xuyun/meta.json +96 -0
- package/prebuilt/xuyun/references/teaching.md +106 -0
- package/prebuilt/xuyun/references/voice.md +96 -0
- package/prebuilt/xuyun/sources/INDEX.md +22 -0
- package/prebuilt/xuyun/sources/lengyanjing-excerpts.md +33 -0
- package/prebuilt/xuyun/sources/tanjing-excerpts.md +32 -0
- package/prebuilt/xuyun/tests/fidelity.jsonl +10 -0
- package/prebuilt/yinguang/SKILL.md +130 -0
- package/prebuilt/yinguang/meta.json +109 -0
- package/prebuilt/yinguang/references/teaching.md +103 -0
- package/prebuilt/yinguang/references/voice.md +96 -0
- package/prebuilt/yinguang/sources/INDEX.md +23 -0
- package/prebuilt/yinguang/sources/jingtu-sanjing-excerpts.md +32 -0
- package/prebuilt/yinguang/sources/wenchao-excerpts.md +50 -0
- package/prebuilt/yinguang/sources/yihanbianfu-excerpts.md +33 -0
- package/prebuilt/yinguang/tests/fidelity.jsonl +10 -0
- package/prebuilt/zhiyi/SKILL.md +130 -0
- package/prebuilt/zhiyi/meta.json +104 -0
- package/prebuilt/zhiyi/references/teaching.md +105 -0
- package/prebuilt/zhiyi/references/voice.md +96 -0
- package/prebuilt/zhiyi/sources/INDEX.md +22 -0
- package/prebuilt/zhiyi/sources/fahua-xuanyi-excerpts.md +103 -0
- package/prebuilt/zhiyi/sources/mohezhiguan-excerpts.md +117 -0
- package/prebuilt/zhiyi/tests/fidelity.jsonl +10 -0
- package/scripts/cite.py +74 -0
- package/scripts/query.py +84 -0
- package/scripts/test-fidelity.py +282 -0
- package/scripts/validate-fidelity.py +146 -0
- package/scripts/validate.py +207 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Master-skill SKILL.md frontmatter linter.
|
|
3
|
+
|
|
4
|
+
Walks prebuilt/<master>/SKILL.md, validates required fields and conventions
|
|
5
|
+
per the Anthropic Agent Skills spec + Master-skill provenance extensions.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python scripts/validate.py # lint all masters
|
|
9
|
+
python scripts/validate.py --master zhiyi # lint one master
|
|
10
|
+
python scripts/validate.py --strict # fail on warnings too
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
PREBUILT_DIR = Path(__file__).resolve().parent.parent / "prebuilt"
|
|
22
|
+
|
|
23
|
+
# --- Required and recommended fields ---
|
|
24
|
+
|
|
25
|
+
REQUIRED_FIELDS = {"name", "description"}
|
|
26
|
+
RECOMMENDED_FIELDS = {"version", "license", "lineage", "dates", "sources", "citation_format"}
|
|
27
|
+
# Fields not applicable to meta-skills (aggregate/comparison skills with no single lineage)
|
|
28
|
+
META_SKILL_EXCLUDED = {"lineage", "dates", "sources", "citation_format"}
|
|
29
|
+
MAX_DESCRIPTION_CHARS = 500
|
|
30
|
+
MAX_SKILL_LINES = 500
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_frontmatter(path: Path) -> tuple[dict, str, list[str]]:
|
|
34
|
+
"""Parse YAML frontmatter from a SKILL.md file.
|
|
35
|
+
|
|
36
|
+
Returns (frontmatter_dict, body, raw_lines).
|
|
37
|
+
"""
|
|
38
|
+
text = path.read_text(encoding="utf-8")
|
|
39
|
+
lines = text.splitlines()
|
|
40
|
+
if not lines or lines[0].strip() != "---":
|
|
41
|
+
return {}, text, lines
|
|
42
|
+
|
|
43
|
+
end = None
|
|
44
|
+
for i, line in enumerate(lines[1:], start=1):
|
|
45
|
+
if line.strip() == "---":
|
|
46
|
+
end = i
|
|
47
|
+
break
|
|
48
|
+
if end is None:
|
|
49
|
+
return {}, text, lines
|
|
50
|
+
|
|
51
|
+
# Minimal YAML parse (no pyyaml dependency)
|
|
52
|
+
fm: dict = {}
|
|
53
|
+
current_key = None
|
|
54
|
+
current_list: list | None = None
|
|
55
|
+
for line in lines[1:end]:
|
|
56
|
+
# list item
|
|
57
|
+
if line.startswith(" - ") and current_key:
|
|
58
|
+
if current_list is None:
|
|
59
|
+
current_list = []
|
|
60
|
+
item = line.strip().lstrip("- ").strip()
|
|
61
|
+
# Try inline dict (title: xxx)
|
|
62
|
+
if ":" in item:
|
|
63
|
+
parts = item.split(":", 1)
|
|
64
|
+
if current_list and isinstance(current_list[-1], dict):
|
|
65
|
+
current_list[-1][parts[0].strip()] = parts[1].strip()
|
|
66
|
+
else:
|
|
67
|
+
current_list.append({parts[0].strip(): parts[1].strip()})
|
|
68
|
+
else:
|
|
69
|
+
current_list.append(item)
|
|
70
|
+
continue
|
|
71
|
+
# Save accumulated list
|
|
72
|
+
if current_list is not None and current_key:
|
|
73
|
+
fm[current_key] = current_list
|
|
74
|
+
current_list = None
|
|
75
|
+
# key: value
|
|
76
|
+
match = re.match(r"^(\w[\w_-]*):\s*(.*)", line)
|
|
77
|
+
if match:
|
|
78
|
+
current_key = match.group(1)
|
|
79
|
+
value = match.group(2).strip().strip('"').strip("'")
|
|
80
|
+
if value:
|
|
81
|
+
fm[current_key] = value
|
|
82
|
+
# If empty value, might be a list starting next line
|
|
83
|
+
# Flush last list
|
|
84
|
+
if current_list is not None and current_key:
|
|
85
|
+
fm[current_key] = current_list
|
|
86
|
+
|
|
87
|
+
body = "\n".join(lines[end + 1 :])
|
|
88
|
+
return fm, body, lines
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def lint_master(master_dir: Path, strict: bool = False) -> list[str]:
|
|
92
|
+
"""Lint a single master directory. Returns list of issues."""
|
|
93
|
+
issues: list[str] = []
|
|
94
|
+
name = master_dir.name
|
|
95
|
+
skill_path = master_dir / "SKILL.md"
|
|
96
|
+
|
|
97
|
+
if not skill_path.exists():
|
|
98
|
+
issues.append(f"[ERROR] {name}: missing SKILL.md")
|
|
99
|
+
return issues
|
|
100
|
+
|
|
101
|
+
fm, body, lines = parse_frontmatter(skill_path)
|
|
102
|
+
|
|
103
|
+
# --- Required fields ---
|
|
104
|
+
for field in REQUIRED_FIELDS:
|
|
105
|
+
if field not in fm:
|
|
106
|
+
issues.append(f"[ERROR] {name}: missing required field '{field}'")
|
|
107
|
+
|
|
108
|
+
# --- Recommended fields ---
|
|
109
|
+
kind = fm.get("kind", "master")
|
|
110
|
+
for field in RECOMMENDED_FIELDS:
|
|
111
|
+
if kind == "meta-skill" and field in META_SKILL_EXCLUDED:
|
|
112
|
+
continue
|
|
113
|
+
if field not in fm:
|
|
114
|
+
issues.append(f"[WARN] {name}: missing recommended field '{field}'")
|
|
115
|
+
|
|
116
|
+
# --- Description length ---
|
|
117
|
+
desc = fm.get("description", "")
|
|
118
|
+
if isinstance(desc, str) and len(desc) > MAX_DESCRIPTION_CHARS:
|
|
119
|
+
issues.append(f"[WARN] {name}: description exceeds {MAX_DESCRIPTION_CHARS} chars ({len(desc)})")
|
|
120
|
+
|
|
121
|
+
# --- SKILL.md line count ---
|
|
122
|
+
if len(lines) > MAX_SKILL_LINES:
|
|
123
|
+
issues.append(f"[WARN] {name}: SKILL.md exceeds {MAX_SKILL_LINES} lines ({len(lines)})")
|
|
124
|
+
|
|
125
|
+
# --- Sources validation ---
|
|
126
|
+
sources = fm.get("sources")
|
|
127
|
+
if isinstance(sources, list):
|
|
128
|
+
for i, src in enumerate(sources):
|
|
129
|
+
if isinstance(src, dict):
|
|
130
|
+
if "title" not in src and "cbeta_id" not in src:
|
|
131
|
+
issues.append(f"[WARN] {name}: sources[{i}] missing 'title' or 'cbeta_id'")
|
|
132
|
+
|
|
133
|
+
# --- Directory structure checks ---
|
|
134
|
+
# Meta-skills (e.g. compare-masters) borrow from other masters and have no own corpus
|
|
135
|
+
if kind != "meta-skill":
|
|
136
|
+
refs_dir = master_dir / "references"
|
|
137
|
+
sources_dir = master_dir / "sources"
|
|
138
|
+
|
|
139
|
+
if not refs_dir.exists():
|
|
140
|
+
issues.append(f"[WARN] {name}: missing references/ directory")
|
|
141
|
+
else:
|
|
142
|
+
if not (refs_dir / "voice.md").exists():
|
|
143
|
+
issues.append(f"[WARN] {name}: missing references/voice.md")
|
|
144
|
+
if not (refs_dir / "teaching.md").exists():
|
|
145
|
+
issues.append(f"[WARN] {name}: missing references/teaching.md")
|
|
146
|
+
|
|
147
|
+
if not sources_dir.exists():
|
|
148
|
+
issues.append(f"[WARN] {name}: missing sources/ directory")
|
|
149
|
+
elif not list(sources_dir.glob("*.md")):
|
|
150
|
+
issues.append(f"[WARN] {name}: sources/ directory is empty")
|
|
151
|
+
|
|
152
|
+
# --- Check for tests ---
|
|
153
|
+
tests_dir = master_dir / "tests"
|
|
154
|
+
if not tests_dir.exists() or not (tests_dir / "fidelity.jsonl").exists():
|
|
155
|
+
issues.append(f"[WARN] {name}: missing tests/fidelity.jsonl")
|
|
156
|
+
|
|
157
|
+
# --- Strict mode: treat warnings as errors ---
|
|
158
|
+
if strict:
|
|
159
|
+
issues = [i.replace("[WARN] ", "[ERROR]") for i in issues]
|
|
160
|
+
|
|
161
|
+
return issues
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main():
|
|
165
|
+
parser = argparse.ArgumentParser(description="Master-skill SKILL.md linter")
|
|
166
|
+
parser.add_argument("--master", type=str, help="Lint a specific master only")
|
|
167
|
+
parser.add_argument("--strict", action="store_true", help="Treat warnings as errors")
|
|
168
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
169
|
+
args = parser.parse_args()
|
|
170
|
+
|
|
171
|
+
if args.master:
|
|
172
|
+
dirs = [PREBUILT_DIR / args.master]
|
|
173
|
+
if not dirs[0].exists():
|
|
174
|
+
print(f"Master '{args.master}' not found in {PREBUILT_DIR}")
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
else:
|
|
177
|
+
dirs = sorted(d for d in PREBUILT_DIR.iterdir() if d.is_dir())
|
|
178
|
+
|
|
179
|
+
all_issues: dict[str, list[str]] = {}
|
|
180
|
+
has_errors = False
|
|
181
|
+
|
|
182
|
+
for d in dirs:
|
|
183
|
+
issues = lint_master(d, strict=args.strict)
|
|
184
|
+
if issues:
|
|
185
|
+
all_issues[d.name] = issues
|
|
186
|
+
if any("[ERROR]" in i for i in issues):
|
|
187
|
+
has_errors = True
|
|
188
|
+
|
|
189
|
+
if args.json:
|
|
190
|
+
print(json.dumps(all_issues, indent=2, ensure_ascii=False))
|
|
191
|
+
else:
|
|
192
|
+
if not all_issues:
|
|
193
|
+
print(f"✅ All {len(dirs)} masters pass validation.")
|
|
194
|
+
else:
|
|
195
|
+
for name, issues in all_issues.items():
|
|
196
|
+
for issue in issues:
|
|
197
|
+
print(issue)
|
|
198
|
+
print()
|
|
199
|
+
total_errors = sum(1 for issues in all_issues.values() for i in issues if "[ERROR]" in i)
|
|
200
|
+
total_warns = sum(1 for issues in all_issues.values() for i in issues if "[WARN]" in i)
|
|
201
|
+
print(f"Summary: {total_errors} error(s), {total_warns} warning(s) across {len(all_issues)} master(s)")
|
|
202
|
+
|
|
203
|
+
sys.exit(1 if has_errors else 0)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
main()
|