master-skill 0.6.0 → 0.9.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.
Files changed (100) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.cursor-plugin/plugin.json +2 -2
  4. package/README.md +71 -14
  5. package/README_EN.md +67 -12
  6. package/SKILL.md +58 -297
  7. package/bin/cli.mjs +58 -12
  8. package/gemini-extension.json +2 -2
  9. package/hooks/session-start +40 -2
  10. package/hooks/tests/test_session_start.sh +149 -0
  11. package/package.json +14 -7
  12. package/prebuilt/compare/SKILL.md +1 -1
  13. package/prebuilt/compare/tests/fidelity.jsonl +13 -0
  14. package/prebuilt/master-ajahn-chah/SKILL.md +35 -1
  15. package/prebuilt/master-ajahn-chah/meta.json +17 -1
  16. package/prebuilt/master-atisha/SKILL.md +35 -1
  17. package/prebuilt/master-atisha/meta.json +17 -1
  18. package/prebuilt/master-buddhaghosa/SKILL.md +35 -1
  19. package/prebuilt/master-buddhaghosa/meta.json +26 -1
  20. package/prebuilt/master-curriculum/SKILL.md +87 -0
  21. package/prebuilt/master-curriculum/references/chan.md +29 -0
  22. package/prebuilt/master-curriculum/references/gelug-madhyamaka.md +33 -0
  23. package/prebuilt/master-curriculum/references/huayan.md +28 -0
  24. package/prebuilt/master-curriculum/references/jingtu.md +29 -0
  25. package/prebuilt/master-curriculum/references/sanlun-zhongguan.md +30 -0
  26. package/prebuilt/master-curriculum/references/theravada-vipassana.md +39 -0
  27. package/prebuilt/master-curriculum/references/tiantai.md +30 -0
  28. package/prebuilt/master-curriculum/references/weishi.md +31 -0
  29. package/prebuilt/master-curriculum/tests/fidelity.jsonl +8 -0
  30. package/prebuilt/master-debate/SKILL.md +274 -0
  31. package/prebuilt/master-debate/meta.json +25 -0
  32. package/prebuilt/master-debate/tests/fidelity.jsonl +8 -0
  33. package/prebuilt/master-fazang/SKILL.md +35 -1
  34. package/prebuilt/master-fazang/meta.json +25 -0
  35. package/prebuilt/master-huineng/SKILL.md +35 -1
  36. package/prebuilt/master-huineng/meta.json +37 -0
  37. package/prebuilt/master-huineng/references/teaching.md +1 -1
  38. package/prebuilt/master-huineng/sources/INDEX.md +1 -1
  39. package/prebuilt/master-huineng/sources/tanjing-excerpts.md +17 -1
  40. package/prebuilt/master-huineng/tests/fidelity.jsonl +2 -0
  41. package/prebuilt/master-kumarajiva/SKILL.md +35 -1
  42. package/prebuilt/master-kumarajiva/meta.json +16 -0
  43. package/prebuilt/master-kumarajiva/references/teaching.md +2 -2
  44. package/prebuilt/master-mahasi-sayadaw/SKILL.md +35 -1
  45. package/prebuilt/master-mahasi-sayadaw/meta.json +17 -1
  46. package/prebuilt/master-milarepa/SKILL.md +35 -1
  47. package/prebuilt/master-milarepa/meta.json +26 -1
  48. package/prebuilt/master-nagarjuna/SKILL.md +170 -0
  49. package/prebuilt/master-nagarjuna/meta.json +129 -0
  50. package/prebuilt/master-nagarjuna/references/teaching.md +112 -0
  51. package/prebuilt/master-nagarjuna/references/voice.md +100 -0
  52. package/prebuilt/master-nagarjuna/sources/INDEX.md +23 -0
  53. package/prebuilt/master-nagarjuna/sources/dazhidulun-excerpts.md +49 -0
  54. package/prebuilt/master-nagarjuna/sources/shizhu-yixing-excerpts.md +33 -0
  55. package/prebuilt/master-nagarjuna/sources/zhonglun-excerpts.md +94 -0
  56. package/prebuilt/master-nagarjuna/tests/fidelity.jsonl +10 -0
  57. package/prebuilt/master-ouyi/SKILL.md +35 -1
  58. package/prebuilt/master-ouyi/meta.json +17 -0
  59. package/prebuilt/master-ouyi/references/teaching.md +4 -4
  60. package/prebuilt/master-ouyi/sources/INDEX.md +1 -1
  61. package/prebuilt/master-ouyi/sources/jiaoguan-gangzong-excerpts.md +3 -3
  62. package/prebuilt/master-tsongkhapa/SKILL.md +35 -1
  63. package/prebuilt/master-tsongkhapa/meta.json +18 -1
  64. package/prebuilt/master-xuanzang/SKILL.md +35 -1
  65. package/prebuilt/master-xuanzang/meta.json +16 -0
  66. package/prebuilt/master-xuanzang/references/teaching.md +5 -5
  67. package/prebuilt/master-xuanzang/references/voice.md +1 -1
  68. package/prebuilt/master-xuyun/SKILL.md +35 -1
  69. package/prebuilt/master-xuyun/meta.json +39 -0
  70. package/prebuilt/master-yinguang/SKILL.md +36 -2
  71. package/prebuilt/master-yinguang/meta.json +17 -0
  72. package/prebuilt/master-yinguang/references/teaching.md +7 -7
  73. package/prebuilt/master-yinguang/references/voice.md +1 -1
  74. package/prebuilt/master-yinguang/sources/INDEX.md +3 -3
  75. package/prebuilt/master-yinguang/sources/wenchao-excerpts.md +4 -4
  76. package/prebuilt/master-yinguang/sources/yihanbianfu-excerpts.md +3 -3
  77. package/prebuilt/master-zhiyi/SKILL.md +35 -1
  78. package/prebuilt/master-zhiyi/meta.json +28 -0
  79. package/prebuilt/master-zhiyi/references/teaching.md +2 -2
  80. package/scripts/_masterpaths.py +32 -0
  81. package/scripts/check-manifest-versions.py +142 -0
  82. package/scripts/cite.py +19 -4
  83. package/scripts/query.py +19 -4
  84. package/scripts/test-fidelity.py +35 -3
  85. package/scripts/tests/test_check_manifest_versions.py +217 -0
  86. package/scripts/tests/test_debate_protocol.py +159 -0
  87. package/scripts/tests/test_injection_hardening.py +174 -0
  88. package/scripts/tests/test_validate_cross_critique.py +149 -0
  89. package/scripts/tests/test_validate_curriculum_sources.py +144 -0
  90. package/scripts/tests/test_validate_lore_triggers_content.py +372 -0
  91. package/scripts/tests/test_validate_persona_fidelity.py +317 -0
  92. package/scripts/tests/test_validate_promptfoo_configs.py +386 -0
  93. package/scripts/validate-cross-critique.py +137 -0
  94. package/scripts/validate-curriculum-sources.py +111 -0
  95. package/scripts/validate-fidelity.py +16 -1
  96. package/scripts/validate-lore-triggers-content.py +393 -0
  97. package/scripts/validate-persona-fidelity.py +210 -0
  98. package/scripts/validate-promptfoo-configs.py +383 -0
  99. package/scripts/validate.py +212 -4
  100. package/scripts/verify_citations.py +152 -0
@@ -0,0 +1,317 @@
1
+ """Tests for validate-persona-fidelity.py.
2
+
3
+ Covers the v0.8 persona-fidelity schema:
4
+ - signature_phrases (required, 3-7 entries, non-empty strings)
5
+ - style (required, dict with all/qa/monologue, each 30-80 zh-Hans chars)
6
+ - lore_triggers (optional, well-formed schema, content length, source_ref reality)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+
17
+
18
+ def _load_module():
19
+ spec_path = Path(__file__).resolve().parents[1] / "validate-persona-fidelity.py"
20
+ spec = importlib.util.spec_from_file_location("vpf", spec_path)
21
+ mod = importlib.util.module_from_spec(spec)
22
+ sys.modules["vpf"] = mod
23
+ spec.loader.exec_module(mod)
24
+ return mod
25
+
26
+
27
+ def _base_meta(**overrides):
28
+ data = {
29
+ "slug": "huineng",
30
+ "sources": [{"type": "cbeta", "id": "T48n2008", "title": "坛经"}],
31
+ "signature_phrases": [
32
+ "本来无一物",
33
+ "明心见性",
34
+ "不立文字",
35
+ "无念为宗",
36
+ ],
37
+ "style": {
38
+ "all": "直指自心,言简意赅;不立繁复名相,于学人执著处直下一锤打破,归于自性本具。",
39
+ "qa": "一两句反问或机锋切入,逼学人回光返照,不给攀缘的概念阶梯,立处即真。",
40
+ "monologue": "上堂直示自性本自清净、本不生灭,三纲领无念无相无住贯穿始终,归结见性成佛。",
41
+ },
42
+ }
43
+ data.update(overrides)
44
+ return data
45
+
46
+
47
+ def _write_master(prebuilt: Path, slug: str, data: dict):
48
+ d = prebuilt / f"master-{slug}"
49
+ d.mkdir(parents=True, exist_ok=True)
50
+ (d / "meta.json").write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
51
+
52
+
53
+ @pytest.fixture
54
+ def fake_tree(tmp_path):
55
+ return tmp_path / "prebuilt"
56
+
57
+
58
+ # ---------- signature_phrases ----------
59
+
60
+
61
+ def test_missing_signature_phrases_fails(fake_tree):
62
+ vpf = _load_module()
63
+ data = _base_meta()
64
+ del data["signature_phrases"]
65
+ _write_master(fake_tree, "huineng", data)
66
+ errors = vpf.validate(fake_tree)
67
+ assert any("signature_phrases" in e and "missing" in e for e in errors)
68
+
69
+
70
+ def test_empty_signature_phrases_fails(fake_tree):
71
+ vpf = _load_module()
72
+ _write_master(fake_tree, "huineng", _base_meta(signature_phrases=[]))
73
+ errors = vpf.validate(fake_tree)
74
+ assert any("signature_phrases" in e for e in errors)
75
+
76
+
77
+ def test_signature_phrases_too_few_fails(fake_tree):
78
+ vpf = _load_module()
79
+ _write_master(fake_tree, "huineng", _base_meta(signature_phrases=["a", "b"]))
80
+ errors = vpf.validate(fake_tree)
81
+ assert any("signature_phrases" in e and "3" in e for e in errors)
82
+
83
+
84
+ def test_signature_phrases_too_many_fails(fake_tree):
85
+ vpf = _load_module()
86
+ _write_master(fake_tree, "huineng", _base_meta(signature_phrases=["p%d" % i for i in range(8)]))
87
+ errors = vpf.validate(fake_tree)
88
+ assert any("signature_phrases" in e and "7" in e for e in errors)
89
+
90
+
91
+ def test_signature_phrases_blank_entry_fails(fake_tree):
92
+ vpf = _load_module()
93
+ _write_master(fake_tree, "huineng", _base_meta(signature_phrases=["a", " ", "c", "d"]))
94
+ errors = vpf.validate(fake_tree)
95
+ assert any("signature_phrases" in e for e in errors)
96
+
97
+
98
+ def test_signature_phrases_wrong_type_fails(fake_tree):
99
+ vpf = _load_module()
100
+ _write_master(fake_tree, "huineng", _base_meta(signature_phrases="not-a-list"))
101
+ errors = vpf.validate(fake_tree)
102
+ assert any("signature_phrases" in e for e in errors)
103
+
104
+
105
+ # ---------- style ----------
106
+
107
+
108
+ def test_missing_style_fails(fake_tree):
109
+ vpf = _load_module()
110
+ data = _base_meta()
111
+ del data["style"]
112
+ _write_master(fake_tree, "huineng", data)
113
+ errors = vpf.validate(fake_tree)
114
+ assert any("style" in e and "missing" in e for e in errors)
115
+
116
+
117
+ def test_style_missing_key_fails(fake_tree):
118
+ vpf = _load_module()
119
+ style = {"all": "x" * 50, "qa": "y" * 50} # missing monologue
120
+ _write_master(fake_tree, "huineng", _base_meta(style=style))
121
+ errors = vpf.validate(fake_tree)
122
+ assert any("monologue" in e for e in errors)
123
+
124
+
125
+ def test_style_extra_key_fails(fake_tree):
126
+ vpf = _load_module()
127
+ style = {"all": "x" * 50, "qa": "y" * 50, "monologue": "z" * 50, "extra": "nope"}
128
+ _write_master(fake_tree, "huineng", _base_meta(style=style))
129
+ errors = vpf.validate(fake_tree)
130
+ assert any("extra" in e for e in errors)
131
+
132
+
133
+ def test_style_value_too_short_fails(fake_tree):
134
+ vpf = _load_module()
135
+ style = {"all": "短", "qa": "y" * 50, "monologue": "z" * 50}
136
+ _write_master(fake_tree, "huineng", _base_meta(style=style))
137
+ errors = vpf.validate(fake_tree)
138
+ assert any("style.all" in e and "30" in e for e in errors)
139
+
140
+
141
+ def test_style_value_too_long_fails(fake_tree):
142
+ vpf = _load_module()
143
+ style = {"all": "x" * 90, "qa": "y" * 50, "monologue": "z" * 50}
144
+ _write_master(fake_tree, "huineng", _base_meta(style=style))
145
+ errors = vpf.validate(fake_tree)
146
+ assert any("style.all" in e and "80" in e for e in errors)
147
+
148
+
149
+ def test_style_value_not_string_fails(fake_tree):
150
+ vpf = _load_module()
151
+ style = {"all": 123, "qa": "y" * 50, "monologue": "z" * 50}
152
+ _write_master(fake_tree, "huineng", _base_meta(style=style))
153
+ errors = vpf.validate(fake_tree)
154
+ assert any("style.all" in e for e in errors)
155
+
156
+
157
+ def test_style_wrong_type_fails(fake_tree):
158
+ vpf = _load_module()
159
+ _write_master(fake_tree, "huineng", _base_meta(style=["not", "a", "dict"]))
160
+ errors = vpf.validate(fake_tree)
161
+ assert any("style" in e for e in errors)
162
+
163
+
164
+ # ---------- lore_triggers (optional) ----------
165
+
166
+
167
+ def test_lore_triggers_absent_is_ok(fake_tree):
168
+ vpf = _load_module()
169
+ _write_master(fake_tree, "huineng", _base_meta())
170
+ errors = vpf.validate(fake_tree)
171
+ assert errors == []
172
+
173
+
174
+ def test_lore_triggers_empty_list_is_ok(fake_tree):
175
+ vpf = _load_module()
176
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[]))
177
+ errors = vpf.validate(fake_tree)
178
+ assert errors == []
179
+
180
+
181
+ def test_lore_triggers_well_formed_passes(fake_tree):
182
+ vpf = _load_module()
183
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
184
+ {
185
+ "keys": ["本来无一物"],
186
+ "content": "x" * 100,
187
+ "source_ref": "T48n2008#般若品",
188
+ }
189
+ ]))
190
+ errors = vpf.validate(fake_tree)
191
+ assert errors == []
192
+
193
+
194
+ def test_lore_triggers_wrong_type_fails(fake_tree):
195
+ vpf = _load_module()
196
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers="not-a-list"))
197
+ errors = vpf.validate(fake_tree)
198
+ assert any("lore_triggers" in e for e in errors)
199
+
200
+
201
+ def test_lore_triggers_entry_not_dict_fails(fake_tree):
202
+ vpf = _load_module()
203
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=["nope"]))
204
+ errors = vpf.validate(fake_tree)
205
+ assert any("lore_triggers" in e for e in errors)
206
+
207
+
208
+ def test_lore_triggers_missing_required_fails(fake_tree):
209
+ vpf = _load_module()
210
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
211
+ {"keys": ["a"], "content": "x" * 100}
212
+ ]))
213
+ errors = vpf.validate(fake_tree)
214
+ assert any("source_ref" in e for e in errors)
215
+
216
+
217
+ def test_lore_triggers_empty_keys_fails(fake_tree):
218
+ vpf = _load_module()
219
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
220
+ {"keys": [], "content": "x" * 100, "source_ref": "T48n2008"}
221
+ ]))
222
+ errors = vpf.validate(fake_tree)
223
+ assert any("keys" in e for e in errors)
224
+
225
+
226
+ def test_lore_triggers_content_too_short_fails(fake_tree):
227
+ vpf = _load_module()
228
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
229
+ {"keys": ["a"], "content": "短", "source_ref": "T48n2008"}
230
+ ]))
231
+ errors = vpf.validate(fake_tree)
232
+ assert any("content" in e and "80" in e for e in errors)
233
+
234
+
235
+ def test_lore_triggers_content_too_long_fails(fake_tree):
236
+ vpf = _load_module()
237
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
238
+ {"keys": ["a"], "content": "x" * 400, "source_ref": "T48n2008"}
239
+ ]))
240
+ errors = vpf.validate(fake_tree)
241
+ assert any("content" in e and "300" in e for e in errors)
242
+
243
+
244
+ def test_lore_triggers_source_ref_not_in_sources_fails(fake_tree):
245
+ vpf = _load_module()
246
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
247
+ {"keys": ["a"], "content": "x" * 100, "source_ref": "T99n9999"}
248
+ ]))
249
+ errors = vpf.validate(fake_tree)
250
+ assert any("source_ref" in e and "T99n9999" in e for e in errors)
251
+
252
+
253
+ def test_lore_triggers_source_ref_with_anchor_resolves(fake_tree):
254
+ vpf = _load_module()
255
+ # `T48n2008#般若品` should still resolve to source id T48n2008.
256
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
257
+ {"keys": ["a"], "content": "x" * 100, "source_ref": "T48n2008#般若品"}
258
+ ]))
259
+ errors = vpf.validate(fake_tree)
260
+ assert errors == []
261
+
262
+
263
+ def test_lore_triggers_secondary_keys_requires_selective(fake_tree):
264
+ vpf = _load_module()
265
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
266
+ {
267
+ "keys": ["a"],
268
+ "secondary_keys": ["b"],
269
+ "content": "x" * 100,
270
+ "source_ref": "T48n2008",
271
+ # selective missing/false → must error
272
+ }
273
+ ]))
274
+ errors = vpf.validate(fake_tree)
275
+ assert any("secondary_keys" in e and "selective" in e for e in errors)
276
+
277
+
278
+ def test_lore_triggers_secondary_keys_with_selective_passes(fake_tree):
279
+ vpf = _load_module()
280
+ _write_master(fake_tree, "huineng", _base_meta(lore_triggers=[
281
+ {
282
+ "keys": ["a"],
283
+ "secondary_keys": ["b"],
284
+ "content": "x" * 100,
285
+ "source_ref": "T48n2008",
286
+ "selective": True,
287
+ }
288
+ ]))
289
+ errors = vpf.validate(fake_tree)
290
+ assert errors == []
291
+
292
+
293
+ # ---------- meta-skill exclusion ----------
294
+
295
+
296
+ def test_meta_skill_without_metajson_is_skipped(fake_tree, tmp_path):
297
+ vpf = _load_module()
298
+ # No meta.json under master-curriculum → validator should not blow up
299
+ (fake_tree / "master-curriculum").mkdir(parents=True)
300
+ _write_master(fake_tree, "huineng", _base_meta())
301
+ errors = vpf.validate(fake_tree)
302
+ assert errors == []
303
+
304
+
305
+ def test_meta_skill_with_kind_marker_is_skipped(fake_tree):
306
+ """master-debate carries a protocol meta.json with kind=meta-skill —
307
+ must not be required to declare persona fields."""
308
+ vpf = _load_module()
309
+ _write_master(fake_tree, "debate", {
310
+ "slug": "debate",
311
+ "kind": "meta-skill",
312
+ "debate_protocol": {"default_rounds": 4},
313
+ })
314
+ _write_master(fake_tree, "huineng", _base_meta())
315
+ errors = vpf.validate(fake_tree)
316
+ assert errors == []
317
+ assert not any("debate" in e for e in errors)