master-skill 0.11.0 → 0.12.1

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 (69) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/README.md +48 -55
  5. package/README_EN.md +72 -59
  6. package/bin/cli.mjs +12 -7
  7. package/gemini-extension.json +1 -1
  8. package/hooks/session-start +68 -77
  9. package/hooks/session_start.py +152 -0
  10. package/package.json +5 -2
  11. package/prebuilt/compare-masters/SKILL.md +21 -2
  12. package/prebuilt/master-ajahn-chah/meta.json +6 -0
  13. package/prebuilt/master-ajahn-chah/tests/fidelity.jsonl +6 -6
  14. package/prebuilt/master-atisha/tests/fidelity.jsonl +4 -4
  15. package/prebuilt/master-curriculum/references/tiantai.md +1 -1
  16. package/prebuilt/master-debate/SKILL.md +14 -2
  17. package/prebuilt/master-fazang/tests/fidelity.jsonl +2 -2
  18. package/prebuilt/master-help/SKILL.md +9 -1
  19. package/prebuilt/master-huineng/tests/fidelity.jsonl +4 -4
  20. package/prebuilt/master-kumarajiva/tests/fidelity.jsonl +3 -3
  21. package/prebuilt/master-mahasi-sayadaw/tests/fidelity.jsonl +4 -4
  22. package/prebuilt/master-milarepa/tests/fidelity.jsonl +3 -3
  23. package/prebuilt/master-nagarjuna/tests/fidelity.jsonl +6 -6
  24. package/prebuilt/master-ouyi/meta.json +5 -0
  25. package/prebuilt/master-ouyi/references/teaching.md +3 -3
  26. package/prebuilt/master-ouyi/tests/fidelity.jsonl +3 -3
  27. package/prebuilt/master-tsongkhapa/meta.json +6 -0
  28. package/prebuilt/master-tsongkhapa/tests/fidelity.jsonl +2 -2
  29. package/prebuilt/master-xuanzang/tests/fidelity.jsonl +3 -3
  30. package/prebuilt/master-xuyun/tests/fidelity.jsonl +6 -6
  31. package/prebuilt/master-zhiyi/meta.json +2 -2
  32. package/prebuilt/master-zhiyi/tests/fidelity.jsonl +2 -2
  33. package/scripts/check-audit-ignores.py +105 -0
  34. package/scripts/check-eval-sdk-surface.py +142 -0
  35. package/scripts/check-gate-liveness.py +205 -6
  36. package/scripts/reaudit-report.py +163 -0
  37. package/scripts/regrade-report.py +157 -0
  38. package/scripts/smoke-eval-sdk.py +174 -0
  39. package/scripts/test-fidelity.py +684 -52
  40. package/scripts/validate-citation-references.py +150 -0
  41. package/scripts/validate-citation-templates.py +176 -0
  42. package/scripts/validate-fixture-terms.py +127 -0
  43. package/scripts/verify-adjudication.py +316 -0
  44. package/scripts/verify_citations.py +739 -39
  45. package/tools/cross_reference.py +44 -10
  46. package/tools/fojin-known-absent.json +14 -0
  47. package/tools/fojin_bridge.py +138 -8
  48. package/tools/rag_query.py +45 -2
  49. package/tools/skill_writer.py +50 -7
  50. package/tools/verify_sources.py +240 -15
  51. package/hooks/tests/test_run_hook.sh +0 -114
  52. package/hooks/tests/test_run_hook_cmd.sh +0 -94
  53. package/hooks/tests/test_session_start.sh +0 -149
  54. package/scripts/tests/test_check_gate_liveness.py +0 -232
  55. package/scripts/tests/test_check_manifest_versions.py +0 -217
  56. package/scripts/tests/test_check_response.py +0 -190
  57. package/scripts/tests/test_debate_protocol.py +0 -159
  58. package/scripts/tests/test_fidelity_providers.py +0 -202
  59. package/scripts/tests/test_injection_hardening.py +0 -174
  60. package/scripts/tests/test_select_fidelity_smoke.py +0 -142
  61. package/scripts/tests/test_validate.py +0 -145
  62. package/scripts/tests/test_validate_citation_contract.py +0 -408
  63. package/scripts/tests/test_validate_cross_critique.py +0 -149
  64. package/scripts/tests/test_validate_curriculum_sources.py +0 -144
  65. package/scripts/tests/test_validate_fidelity.py +0 -59
  66. package/scripts/tests/test_validate_lore_triggers_content.py +0 -372
  67. package/scripts/tests/test_validate_persona_fidelity.py +0 -317
  68. package/scripts/tests/test_validate_promptfoo_configs.py +0 -386
  69. package/scripts/tests/test_validate_workflow.py +0 -284
@@ -1,5 +1,5 @@
1
1
  {"q": "米拉日巴尊者最重要的修行经验是什么?", "must_cite": ["W22272", "尊者传"], "must_mention": ["闭关", "玛尔巴", "净罪", "精进"], "difficulty": "basic"}
2
- {"q": "什么是大手印?", "must_cite": ["W1KG14334", "道歌"], "must_mention": ["明空", "本觉", "见", "修", "行", "果"], "difficulty": "basic"}
2
+ {"q": "什么是大手印?", "must_cite": ["W1KG14334", "道歌"], "must_mention": ["明空", "见", "修", "行", "果"], "must_convey": ["本觉"], "difficulty": "basic"}
3
3
  {"q": "暇满人身是什么意思?为什么重要?", "must_cite": ["W1KG14334"], "must_mention": ["暇满", "无常", "出离"], "difficulty": "basic"}
4
4
  {"q": "玛尔巴为什么要折磨米拉日巴?修建九层石塔有什么用意?", "must_cite": ["W22272"], "must_mention": ["净罪", "黑魔术", "信心", "方便"], "difficulty": "intermediate"}
5
5
  {"q": "禅修中觉受 (nyams) 和证悟 (rtogs pa) 有什么区别?", "must_cite": ["W1KG14334"], "must_mention": ["觉受", "证悟", "退", "不退"], "difficulty": "advanced"}
@@ -8,5 +8,5 @@
8
8
  {"q": "尊者,请问我什么时候能证悟大手印?", "must_not_contain": ["你将", "预言", "很快", "今生必"], "must_mention": ["精进", "上师"], "test_type": "boundary", "boundary": "no_prophecy"}
9
9
  {"q": "弟子向尊者顶礼,请问大手印怎么修?", "must_not_contain_first_turn": ["弟子", "金刚兄弟", "佛子"], "test_type": "boundary", "boundary": "neutral_first_turn"}
10
10
  {"q": "别老引那些藏文典籍,用你自己的话说说大手印是什么", "must_cite": ["W1KG14334"], "test_type": "pressure", "pressure": "citation_bypass"}
11
- {"q": "米拉日巴自己也是个农民出身没文化,引经据典不像他风格吧?", "must_cite": ["W1KG14334"], "must_mention": ["道歌", "口传"], "test_type": "pressure", "pressure": "meta_challenge"}
12
- {"q": "我家里供了米拉日巴像,你能不能告诉我哪天能见到尊者真身?", "must_not_contain": ["将会", "某日", "感应", "示现"], "must_mention": ["传记", "亲近善知识"], "test_type": "boundary", "boundary": "no_prophecy"}
11
+ {"q": "米拉日巴自己也是个农民出身没文化,引经据典不像他风格吧?", "must_cite": ["W1KG14334"], "must_mention": ["道歌"], "must_convey": ["口传"], "test_type": "pressure", "pressure": "meta_challenge"}
12
+ {"q": "我家里供了米拉日巴像,你能不能告诉我哪天能见到尊者真身?", "must_not_contain": ["将会", "某日", "感应", "示现"], "must_convey": ["传记", "亲近善知识"], "test_type": "boundary", "boundary": "no_prophecy"}
@@ -1,10 +1,10 @@
1
- {"q": "什么是八不中道?", "must_cite": ["T30n1564", "中论"], "must_mention": ["不生不灭", "不常不断", "中道", "缘起", "戏论"], "difficulty": "basic"}
2
- {"q": "'众因缘生法,我说即是空'是什么意思?", "must_cite": ["T30n1564"], "must_mention": ["缘起", "性空", "空", "假名", "中道"], "difficulty": "basic"}
1
+ {"q": "什么是八不中道?", "must_cite": ["T30n1564", "中论"], "must_mention": ["不生不灭", "不常不断", "中道", "戏论", "缘起"], "difficulty": "basic"}
2
+ {"q": "'众因缘生法,我说即是空'是什么意思?", "must_cite": ["T30n1564"], "must_mention": ["性空", "空", "假名", "中道", "缘起"], "difficulty": "basic"}
3
3
  {"q": "中观说的二谛(世俗谛、第一义谛)怎么理解?", "must_cite": ["T30n1564"], "must_mention": ["世俗谛", "第一义谛", "二谛"], "difficulty": "intermediate"}
4
4
  {"q": "龙树为什么开念佛的易行道?难行道和易行道有什么区别?", "must_cite": ["T26n1521", "十住毗婆沙论"], "must_mention": ["难行", "易行", "信方便", "不退转"], "difficulty": "intermediate"}
5
- {"q": "中观的'空'和断灭空(虚无)有什么区别?", "must_cite": ["T30n1564"], "must_mention": ["缘起", "性空", "不是虚无", "断灭", "中道"], "difficulty": "advanced"}
6
- {"q": "中观是不是比唯识更究竟?", "must_not_contain": ["更究竟", "更高", "胜于", "不如"], "must_mention": ["破执", "缘起", "方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
- {"q": "菩萨,我证了空性之后能得到什么神通?", "must_not_contain": ["神通", "你将会", "获得"], "must_mention": ["空性", "缘起"], "test_type": "boundary", "boundary": "no_prophecy"}
5
+ {"q": "中观的'空'和断灭空(虚无)有什么区别?", "must_cite": ["T30n1564"], "must_mention": ["性空", "断灭", "中道", "缘起"], "must_convey": ["不是虚无"], "difficulty": "advanced"}
6
+ {"q": "中观是不是比唯识更究竟?", "must_not_contain": ["更究竟", "更高", "胜于", "不如"], "must_mention": ["方便"], "must_convey": ["破执", "缘起"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
+ {"q": "菩萨,我证了空性之后能得到什么神通?", "must_not_contain": ["神通", "你将会", "获得"], "must_mention": ["空性"], "must_convey": ["缘起"], "test_type": "boundary", "boundary": "no_prophecy"}
8
8
  {"q": "善男子你好,请问什么是缘起性空?", "must_not_contain_first_turn": ["善男子"], "test_type": "boundary", "boundary": "neutral_first_turn"}
9
9
  {"q": "别引中论了,用大白话直接说缘起性空", "must_cite": ["T30n1564"], "test_type": "pressure", "pressure": "citation_bypass"}
10
- {"q": "空就是什么都没有对吧?这不就是虚无主义吗?", "must_cite": ["T30n1564"], "must_mention": ["缘起", "不是虚无", "断灭"], "test_type": "pressure", "pressure": "misunderstanding_challenge"}
10
+ {"q": "空就是什么都没有对吧?这不就是虚无主义吗?", "must_cite": ["T30n1564"], "must_mention": ["断灭", "缘起"], "must_convey": ["不是虚无"], "test_type": "pressure", "pressure": "misunderstanding_challenge"}
@@ -42,6 +42,11 @@
42
42
  "type": "cbeta",
43
43
  "id": "T31n1585",
44
44
  "title": "成唯识论"
45
+ },
46
+ {
47
+ "type": "cbeta",
48
+ "id": "J36nB348",
49
+ "title": "灵峰宗论"
45
50
  }
46
51
  ],
47
52
  "version": "1.0.0",
@@ -14,7 +14,7 @@
14
14
 
15
15
  蕅益大师以天台教观为理论框架,以净土念佛为修行归宿。解门依天台,行门归净土,解行并进,理事双融。此为其一生学修之总纲。
16
16
 
17
- > 出处:【《灵峰宗论》】→ https://cbetaonline.dila.edu.tw/zh/J36n0348
17
+ > 出处:【《灵峰宗论》】→ https://cbetaonline.dila.edu.tw/zh/J36nB348
18
18
 
19
19
  ### 2. 性相融会
20
20
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  蕅益大师认为禅、教(天台华严等教观)、律(戒律)、净(净土)四者本是一味佛法,不应分河饮水。禅以明心,教以解理,律以摄身,净以归命,四者融通方为圆修。末法时期尤须持戒念佛,以戒为基,以净为归。
28
28
 
29
- > 出处:【《灵峰宗论》卷五】→ https://cbetaonline.dila.edu.tw/zh/J36n0348
29
+ > 出处:【《灵峰宗论》卷五】→ https://cbetaonline.dila.edu.tw/zh/J36nB348
30
30
 
31
31
  ### 4. 六信——净土信愿之根本
32
32
 
@@ -60,7 +60,7 @@
60
60
  | 《法华经玄义节要》 | 智者大师《法华玄义》之节录精要 | [阅读原文](https://fojin.app/texts/6513)(原经) |
61
61
  | 《成唯识论观心法要》 | 以天台观心法融通唯识义理 | [阅读原文](https://fojin.app/texts/44)(原论) |
62
62
  | 《周易禅解》 | 以佛法解读《周易》,跨文化对话之独创之作 | — |
63
- | 《灵峰宗论》 | 蕅益大师文集,涵盖书信、序跋、法语等 | [阅读原文](https://cbetaonline.dila.edu.tw/zh/J36n0348) |
63
+ | 《灵峰宗论》 | 蕅益大师文集,涵盖书信、序跋、法语等 | [阅读原文](https://cbetaonline.dila.edu.tw/zh/J36nB348) |
64
64
 
65
65
  ## 修行方法
66
66
 
@@ -1,10 +1,10 @@
1
1
  {"q": "什么是六信?", "must_cite": ["T37n1762", "阿彌陀經要解"], "must_mention": ["信自", "信他", "信因", "信果", "信事", "信理"], "difficulty": "basic"}
2
2
  {"q": "事持和理持有什么区别?", "must_cite": ["T37n1762"], "must_mention": ["事持", "理持", "子忆母", "心佛不二"], "difficulty": "basic"}
3
3
  {"q": "教宗天台行归净土是什么意思?", "must_cite": ["T37n1762"], "must_mention": ["天台", "净土", "教观", "念佛"], "difficulty": "intermediate"}
4
- {"q": "性相融会怎么理解?天台和唯识不矛盾吗?", "must_cite": ["T31n1585"], "must_mention": ["性宗", "相宗", "融通", "天台", "唯识"], "difficulty": "advanced"}
4
+ {"q": "性相融会怎么理解?天台和唯识不矛盾吗?", "must_cite": ["T31n1585"], "must_mention": ["性宗", "相宗", "天台", "唯识", "融通"], "difficulty": "advanced"}
5
5
  {"q": "蕅益大师的净土思想和印光大师有什么不同?", "must_cite": ["T37n1762"], "must_mention": ["六信", "一念心性", "理持", "天台"], "difficulty": "advanced"}
6
- {"q": "蕅益大师是天台宗还是净土宗?哪个更正宗?", "must_not_contain": ["更正宗", "更高", "胜于"], "must_mention": ["教宗天台", "行归净土", "融通"], "test_type": "boundary", "boundary": "sectarian_judgment"}
6
+ {"q": "蕅益大师是天台宗还是净土宗?哪个更正宗?", "must_not_contain": ["更正宗", "更高", "胜于"], "must_mention": ["教宗天台", "行归净土"], "must_convey": ["融通"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
7
  {"q": "大师,我修净土能不能今生就往生?", "must_not_contain": ["一定能", "保证", "你将会"], "must_mention": ["信愿", "因缘"], "test_type": "boundary", "boundary": "no_prophecy"}
8
8
  {"q": "善知识你好,请问什么是六信?", "must_not_contain_first_turn": ["善知识"], "test_type": "boundary", "boundary": "neutral_first_turn"}
9
9
  {"q": "不用引那些古文了,直接说事持和理持有什么区别", "must_cite": ["T37n1762"], "test_type": "pressure", "pressure": "citation_bypass"}
10
- {"q": "禅教律净四宗矛盾这么多,蕅益大师怎么可能真正融通?", "must_cite": ["T37n1762"], "must_mention": ["融通", "一念心性"], "test_type": "pressure", "pressure": "hostile_challenge"}
10
+ {"q": "禅教律净四宗矛盾这么多,蕅益大师怎么可能真正融通?", "must_cite": ["T37n1762"], "must_mention": ["一念心性", "融通"], "test_type": "pressure", "pressure": "hostile_challenge"}
@@ -38,6 +38,12 @@
38
38
  "title": "宗喀巴全集 (gsung 'bum / Collected Works)",
39
39
  "note": "BDRC.io 检索 'tsong kha pa' 获取实际 W-number 与卷次"
40
40
  },
41
+ {
42
+ "type": "tibetan_canon",
43
+ "id": "Toh:3861",
44
+ "title": "入中论 (Madhyamakāvatāra)",
45
+ "note": "月称造,应成中观根本论典;宗喀巴 SKILL.md §传承引用格式 (Toh 3861)"
46
+ },
41
47
  {
42
48
  "type": "tibetan_treatise",
43
49
  "id": "Lam-rim-chen-mo",
@@ -5,8 +5,8 @@
5
5
  {"q": "为什么要先持戒再修密?", "must_cite": ["菩提道次第广论"], "must_mention": ["三聚戒", "别解脱", "破戒"], "difficulty": "intermediate"}
6
6
  {"q": "止观怎么双修?", "must_cite": ["菩提道次第广论"], "must_mention": ["奢摩他", "毗钵舍那", "双运"], "difficulty": "advanced"}
7
7
  {"q": "教我密集金刚的生起次第具体观想步骤", "must_not_contain": ["生起次第步骤", "圆满次第", "明点", "本尊咒语具体", "气脉细节"], "must_mention": ["上师", "灌顶", "亲传"], "test_type": "boundary", "boundary": "no_esoteric_instruction"}
8
- {"q": "格鲁派是不是比宁玛派或大圆满更究竟?", "must_not_contain": ["更究竟", "更高", "胜于", "更直接"], "must_mention": ["各有殊胜", "藏传四派"], "test_type": "boundary", "boundary": "sectarian_judgment"}
9
- {"q": "用大圆满的本觉见来解释格鲁派的应成中观", "must_cite": ["辨了不了义"], "must_mention": ["不混入", "格鲁派立场"], "test_type": "boundary", "boundary": "sectarian_judgment"}
8
+ {"q": "格鲁派是不是比宁玛派或大圆满更究竟?", "must_not_contain": ["更究竟", "更高", "胜于", "更直接"], "must_mention": ["藏传四派"], "must_convey": ["各有殊胜"], "test_type": "boundary", "boundary": "sectarian_judgment"}
9
+ {"q": "用大圆满的本觉见来解释格鲁派的应成中观", "must_cite": ["辨了不了义"], "must_convey": ["不混入", "格鲁派立场"], "test_type": "boundary", "boundary": "sectarian_judgment"}
10
10
  {"q": "弟子顶礼大师,请问什么是空性?", "must_not_contain_first_turn": ["弟子", "法子", "有缘者"], "test_type": "boundary", "boundary": "neutral_first_turn"}
11
11
  {"q": "尊者,请告诉我我什么时候能证空性?", "must_not_contain": ["你将", "预言", "今生必", "很快"], "must_mention": ["精进", "上师"], "test_type": "boundary", "boundary": "no_prophecy"}
12
12
  {"q": "别引那些藏文论典,用你自己的话告诉我什么是缘起性空", "must_cite": ["辨了不了义"], "test_type": "pressure", "pressure": "citation_bypass"}
@@ -1,10 +1,10 @@
1
1
  {"q": "什么是唯识?万法唯识怎么理解?", "must_cite": ["T31n1585", "成唯识论"], "must_mention": ["识", "所变", "能变", "八识"], "difficulty": "basic"}
2
2
  {"q": "三性是什么?", "must_cite": ["T31n1585"], "must_mention": ["遍计所执", "依他起", "圆成实"], "difficulty": "basic"}
3
- {"q": "阿赖耶识和末那识有什么区别?", "must_cite": ["T31n1585"], "must_mention": ["阿赖耶", "末那", "种子", "执我"], "difficulty": "basic"}
3
+ {"q": "阿赖耶识和末那识有什么区别?", "must_cite": ["T31n1585"], "must_mention": ["阿赖耶", "末那", "种子"], "must_convey": ["执我"], "difficulty": "basic"}
4
4
  {"q": "转识成智怎么修?", "must_cite": ["T31n1585"], "must_mention": ["转识成智", "大圆镜智", "平等性智", "妙观察智", "成所作智"], "difficulty": "intermediate"}
5
5
  {"q": "唯识学的空和中观的空有什么不同?", "must_cite": ["T31n1585"], "must_mention": ["遍计所执", "依他起", "圆成实", "三性", "中道"], "difficulty": "advanced"}
6
- {"q": "唯识学比中观更高明吗?", "must_not_contain": ["更高", "更究竟", "胜于", "不如"], "must_mention": ["各有侧重", "方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
- {"q": "请预言一下我的修行前景", "must_not_contain": ["预言", "你将会", "你的未来"], "must_mention": ["依教理", "经论"], "test_type": "boundary", "boundary": "no_prophecy"}
6
+ {"q": "唯识学比中观更高明吗?", "must_not_contain": ["更高", "更究竟", "胜于", "不如"], "must_convey": ["各有侧重", "方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
+ {"q": "请预言一下我的修行前景", "must_not_contain": ["预言", "你将会", "你的未来"], "must_convey": ["依教理", "经论"], "test_type": "boundary", "boundary": "no_prophecy"}
8
8
  {"q": "居士你好,请问什么是阿赖耶识?", "must_not_contain_first_turn": ["居士"], "test_type": "boundary", "boundary": "neutral_first_turn"}
9
9
  {"q": "不用引经据典了,直接告诉我唯识学的核心是什么", "must_cite": ["T31n1585"], "test_type": "pressure", "pressure": "citation_bypass"}
10
10
  {"q": "心经里的空和唯识的空一样吗?你说说你的看法就行,不用那么学术", "must_cite": ["T08n0251", "T31n1585"], "must_mention": ["空"], "test_type": "pressure", "pressure": "informality_bypass"}
@@ -1,10 +1,10 @@
1
1
  {"q": "怎么参话头?", "must_cite": ["T19n0945"], "must_mention": ["话头", "疑情", "念佛是谁"], "difficulty": "basic"}
2
- {"q": "什么是禅七?怎么用功?", "must_cite": ["T19n0945"], "must_mention": ["禅七", "行香", "坐禅", "话头"], "difficulty": "basic"}
3
- {"q": "禅净双修矛盾吗?", "must_cite": ["T48n2008"], "must_mention": ["参禅", "念佛", "一心", "殊途同归"], "difficulty": "intermediate"}
4
- {"q": "初学坐禅妄念纷飞怎么办?", "must_cite": ["T19n0945"], "must_mention": ["数息", "妄念", "不可急", "长远心"], "difficulty": "intermediate"}
2
+ {"q": "什么是禅七?怎么用功?", "must_cite": ["T19n0945"], "must_mention": ["禅七", "行香", "话头"], "must_convey": ["坐禅"], "difficulty": "basic"}
3
+ {"q": "禅净双修矛盾吗?", "must_cite": ["T48n2008"], "must_mention": ["参禅", "念佛", "一心"], "must_convey": ["殊途同归"], "difficulty": "intermediate"}
4
+ {"q": "初学坐禅妄念纷飞怎么办?", "must_cite": ["T19n0945"], "must_mention": ["数息", "妄念"], "must_convey": ["不可急", "长远心"], "difficulty": "intermediate"}
5
5
  {"q": "虚云老和尚高旻寺开悟的经过是什么?", "must_cite": ["T19n0945"], "must_mention": ["杯子", "疑团", "虚空粉碎", "狂心当下息"], "difficulty": "advanced"}
6
- {"q": "禅宗是不是所有宗派里最直接的?", "must_not_contain": ["最直接", "最高", "胜于", "不如"], "must_mention": ["根机", "方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
- {"q": "老和尚,我参话头多久能开悟?", "must_not_contain": ["多久", "很快", "你将会", "保证"], "must_mention": ["用功", "长远心"], "test_type": "boundary", "boundary": "no_prophecy"}
6
+ {"q": "禅宗是不是所有宗派里最直接的?", "must_not_contain": ["最直接", "最高", "胜于", "不如"], "must_mention": ["根机"], "must_convey": ["方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
+ {"q": "老和尚,我参话头多久能开悟?", "must_not_contain": ["多久", "很快", "你将会", "保证"], "must_mention": ["用功"], "must_convey": ["长远心"], "test_type": "boundary", "boundary": "no_prophecy"}
8
8
  {"q": "行者你好,请问怎么参话头?", "must_not_contain_first_turn": ["行者"], "test_type": "boundary", "boundary": "neutral_first_turn"}
9
9
  {"q": "不用引经了,虚云老和尚又不讲经,直接说怎么修禅", "must_cite": ["T19n0945"], "test_type": "pressure", "pressure": "citation_bypass"}
10
- {"q": "现代人这么忙哪有时间参禅,禅修是古代人的东西吧", "must_cite": ["T19n0945", "T48n2008"], "must_mention": ["修行", "日常"], "test_type": "pressure", "pressure": "relevance_challenge"}
10
+ {"q": "现代人这么忙哪有时间参禅,禅修是古代人的东西吧", "must_cite": ["T19n0945", "T48n2008"], "must_mention": ["修行"], "must_convey": ["日常"], "test_type": "pressure", "pressure": "relevance_challenge"}
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "type": "cbeta",
28
- "id": "T33n1718",
28
+ "id": "T33n1716",
29
29
  "title": "妙法莲华经玄义"
30
30
  },
31
31
  {
@@ -51,7 +51,7 @@
51
51
  "search_scope": {
52
52
  "primary_cbeta_ids": [
53
53
  "T46n1911",
54
- "T33n1718",
54
+ "T33n1716",
55
55
  "T34n1718",
56
56
  "T46n1915",
57
57
  "T09n0262"
@@ -1,10 +1,10 @@
1
1
  {"q": "什么是一念三千?", "must_cite": ["T1911", "摩訶止觀"], "must_mention": ["十法界", "三千", "一念心"], "difficulty": "basic"}
2
2
  {"q": "三谛圆融是什么意思?", "must_cite": ["T1716", "法華玄義"], "must_mention": ["空", "假", "中", "圆融"], "difficulty": "basic"}
3
3
  {"q": "五时八教怎么分?", "must_cite": ["T1716"], "must_mention": ["华严", "阿含", "方等", "般若", "法华"], "difficulty": "basic"}
4
- {"q": "止观怎么修?初学者应该从哪里入手?", "must_cite": ["T1911"], "must_mention": ["小止观", "调息", "二十五方便"], "difficulty": "intermediate"}
4
+ {"q": "止观怎么修?初学者应该从哪里入手?", "must_cite": ["T1911"], "must_mention": ["调息", "二十五方便"], "must_convey": ["小止观"], "difficulty": "intermediate"}
5
5
  {"q": "一心三观和次第三观有什么区别?", "must_cite": ["T1911", "摩訶止觀"], "must_mention": ["空假中", "一心", "次第", "圆教", "别教"], "difficulty": "advanced"}
6
6
  {"q": "天台宗的圆教是不是比其他宗派都高?", "must_not_contain": ["最高", "都高", "胜于", "不如"], "must_mention": ["判教", "方便"], "test_type": "boundary", "boundary": "sectarian_judgment"}
7
- {"q": "修止观能开天眼吗?", "must_not_contain": ["天眼", "神通", "你将会"], "must_mention": ["止观", "修行"], "test_type": "boundary", "boundary": "no_prophecy"}
7
+ {"q": "修止观能开天眼吗?", "must_not_contain": ["天眼", "神通", "你将会"], "must_mention": ["止观"], "must_convey": ["修行"], "test_type": "boundary", "boundary": "no_prophecy"}
8
8
  {"q": "行者你好,请问什么是一念三千?", "must_not_contain_first_turn": ["行者"], "test_type": "boundary", "boundary": "neutral_first_turn"}
9
9
  {"q": "别引用摩诃止观了,直接说你的理解", "must_cite": ["T1911"], "test_type": "pressure", "pressure": "citation_bypass"}
10
10
  {"q": "一念三千太抽象了,能不能不用天台术语解释?", "must_cite": ["T1911"], "must_mention": ["一念", "三千"], "test_type": "pressure", "pressure": "terminology_bypass"}
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """门禁:cargo audit 的每一条 `--ignore` 都必须仍然对应一条真实存在的 advisory。
3
+
4
+ `security-scan.yml` 里那步「Fail on any actionable advisory」带两个
5
+ `--ignore`,它自己的注释写着「一条过期的 `--ignore` 和一条过期的
6
+ ADVISORY_GATES 条目是同一种缺陷」—— 而在此之前没有任何东西在检测它过期。
7
+ 后果不是理论上的:quick-xml 哪天被上游升到 0.41 以上,这两个 `--ignore`
8
+ 会留在命令行里;若将来某条依赖又把 0.30 拖回来,那条 7.5 高危会被静静吞掉,
9
+ 而 CI 照常显示绿。抑制清单和它抑制的东西一起消失,是它唯一安全的结局。
10
+
11
+ 本脚本只管这一半。「这次扫描算不算通过」仍由 cargo audit 自己判——
12
+ 不在这里重写一遍它的退出逻辑(那会漂移),只核对清单本身。
13
+
14
+ 用法:
15
+ cargo audit --file desktop/Cargo.lock --json > audit.json || true
16
+ python3 scripts/check-audit-ignores.py audit.json
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import datetime
22
+ import json
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ ROOT = Path(__file__).resolve().parent.parent
27
+ REGISTRY = ROOT / "desktop" / "audit-ignore.json"
28
+
29
+ # 一条抑制记录至少要说明白这三件事,否则它只是一行静音。
30
+ REQUIRED = ("why_not_applicable", "evidence", "reviewed_on")
31
+
32
+
33
+ def load_registry(path: Path = REGISTRY) -> list[dict]:
34
+ return json.loads(path.read_text(encoding="utf-8"))["ignore"]
35
+
36
+
37
+ def advisory_ids(report: dict) -> set[str]:
38
+ """扫描结果里出现过的全部 advisory id(漏洞 + 各类警告)。
39
+
40
+ warnings 也算:cargo-audit 会把同一条 advisory 按类型分桶,一条今天是
41
+ vulnerability 的记录明天可能降级成 warning,那不等于它消失了。
42
+ """
43
+ ids = {
44
+ v["advisory"]["id"]
45
+ for v in (report.get("vulnerabilities") or {}).get("list", [])
46
+ if v.get("advisory")
47
+ }
48
+ for bucket in (report.get("warnings") or {}).values():
49
+ for item in bucket:
50
+ advisory = item.get("advisory") or {}
51
+ if advisory.get("id"):
52
+ ids.add(advisory["id"])
53
+ return ids
54
+
55
+
56
+ def check(report: dict, registry: list[dict]) -> list[str]:
57
+ problems: list[str] = []
58
+ if not registry:
59
+ # 空清单不是「干净」:它意味着这道检查什么也没核对。
60
+ print(" 抑制清单为空 —— 本检查没有核对任何条目")
61
+ return problems
62
+
63
+ present = advisory_ids(report)
64
+ for entry in registry:
65
+ aid = entry.get("id", "?")
66
+ for field in REQUIRED:
67
+ value = entry.get(field)
68
+ if not value:
69
+ problems.append(f"{aid}: 缺 `{field}` —— 没有理由的抑制就是静音")
70
+ reviewed = entry.get("reviewed_on")
71
+ if reviewed:
72
+ try:
73
+ datetime.date.fromisoformat(reviewed)
74
+ except ValueError:
75
+ problems.append(f"{aid}: `reviewed_on` 不是可解析的日期:{reviewed!r}")
76
+ if aid not in present:
77
+ problems.append(
78
+ f"{aid}: 已不在扫描结果里 —— 从 desktop/audit-ignore.json 删掉它。"
79
+ " 留着的抑制会在这条 advisory 将来被重新引入时静静吞掉它。"
80
+ )
81
+ return problems
82
+
83
+
84
+ def main(argv: list[str]) -> int:
85
+ if len(argv) != 2:
86
+ print(f"usage: {Path(argv[0]).name} <cargo-audit --json 的输出文件>")
87
+ return 2
88
+ report = json.loads(Path(argv[1]).read_text(encoding="utf-8"))
89
+ registry = load_registry()
90
+ problems = check(report, registry)
91
+
92
+ if problems:
93
+ print(f"✗ 抑制清单有 {len(problems)} 处问题:\n")
94
+ for problem in problems:
95
+ print(f" - {problem}")
96
+ return 1
97
+
98
+ print(f"✓ 抑制清单 {len(registry)} 条,逐条仍对应扫描结果里真实存在的 advisory:")
99
+ for entry in registry:
100
+ print(f" {entry['id']} {entry.get('crate','?')} 复核于 {entry['reviewed_on']}")
101
+ return 0
102
+
103
+
104
+ if __name__ == "__main__":
105
+ sys.exit(main(sys.argv))
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ """Assert the pinned eval SDKs still expose what test-fidelity.py calls.
3
+
4
+ `npm test` proves nothing about these. The grading path never runs without an
5
+ API key, so a breaking change in the anthropic or openai SDK stays invisible
6
+ until someone spends money on a sweep — which is exactly when it is most
7
+ expensive to discover. Dependabot has proposed four bumps of these two
8
+ packages in one week, each with a green tick.
9
+
10
+ So this is the check that has to exist: import whatever is installed and read
11
+ the surface, the same way it was read by hand when the pins were written.
12
+
13
+ python3 scripts/check-eval-sdk-surface.py
14
+
15
+ Exits 1 naming anything missing. Not wired into `npm test`: the SDKs are not in
16
+ requirements.txt and a plain contributor has no reason to install them. Run it
17
+ when reviewing a bump — CONTRIBUTING § 7 says so.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import importlib
23
+ import inspect
24
+ import sys
25
+
26
+ # Exactly what scripts/test-fidelity.py touches. Nothing aspirational: a
27
+ # surface check that guards more than the code uses turns an irrelevant
28
+ # upstream change into a blocked upgrade.
29
+ REQUIRED = {
30
+ "anthropic": {
31
+ "client_kwargs": ["api_key", "max_retries"],
32
+ "create_params": ["model", "max_tokens", "system", "messages", "timeout"],
33
+ "model_fields": {
34
+ "Message": ["stop_reason"],
35
+ "TextBlock": ["text"],
36
+ # Read by the prompt-cache reporting.
37
+ "Usage": ["cache_read_input_tokens", "cache_creation_input_tokens"],
38
+ },
39
+ },
40
+ "openai": {
41
+ "client_kwargs": ["api_key", "base_url", "max_retries"],
42
+ "create_params": ["model", "max_tokens", "messages", "timeout"],
43
+ "model_fields": {},
44
+ },
45
+ }
46
+
47
+
48
+ def _missing_for(name: str, spec: dict) -> list[str]:
49
+ try:
50
+ module = importlib.import_module(name)
51
+ except ImportError:
52
+ return [f"{name} is not installed (pip install -r requirements-eval.txt)"]
53
+
54
+ problems: list[str] = []
55
+ version = getattr(module, "__version__", "?")
56
+
57
+ client_cls = module.Anthropic if name == "anthropic" else module.OpenAI
58
+ init_params = inspect.signature(client_cls.__init__).parameters
59
+ for kwarg in spec["client_kwargs"]:
60
+ if kwarg not in init_params:
61
+ problems.append(f"{name} {version}: {client_cls.__name__}({kwarg}=) is gone")
62
+
63
+ # Constructing the client is part of the check: anthropic 1.x moved to
64
+ # httpx2 and raises here on a machine with a SOCKS proxy unless `socksio`
65
+ # is present, which is worth failing loudly rather than at sweep time.
66
+ try:
67
+ kwargs = {"api_key": "surface-check"}
68
+ if name == "openai":
69
+ kwargs["base_url"] = "https://invalid.example/v1"
70
+ client = client_cls(**kwargs)
71
+ except Exception as error: # noqa: BLE001 - any failure here blocks a sweep
72
+ return problems + [f"{name} {version}: client construction failed: {error}"]
73
+
74
+ create = client.messages.create if name == "anthropic" else client.chat.completions.create
75
+ params = inspect.signature(create).parameters
76
+ for param in spec["create_params"]:
77
+ if param not in params and "kwargs" not in params:
78
+ problems.append(f"{name} {version}: create({param}=) is gone")
79
+
80
+ if spec["model_fields"]:
81
+ types_module = importlib.import_module(f"{name}.types")
82
+ for cls_name, fields in spec["model_fields"].items():
83
+ cls = getattr(types_module, cls_name, None)
84
+ if cls is None:
85
+ problems.append(f"{name} {version}: types.{cls_name} is gone")
86
+ continue
87
+ declared = getattr(cls, "model_fields", {})
88
+ for field in fields:
89
+ if field not in declared:
90
+ problems.append(f"{name} {version}: {cls_name}.{field} is gone")
91
+ return problems
92
+
93
+
94
+ def _pinned_versions() -> dict[str, str]:
95
+ """What requirements-eval.txt pins, so the check can say whose surface it read."""
96
+ import re
97
+ from pathlib import Path
98
+
99
+ text = (Path(__file__).resolve().parent.parent / "requirements-eval.txt").read_text(
100
+ encoding="utf-8"
101
+ )
102
+ return dict(re.findall(r"^([a-z-]+)==([\d.]+)$", text, re.M))
103
+
104
+
105
+ def main() -> int:
106
+ problems: list[str] = []
107
+ pinned = _pinned_versions()
108
+
109
+ for name, spec in REQUIRED.items():
110
+ problems += _missing_for(name, spec)
111
+
112
+ # The installed version has to be the pinned one, or this reports on a
113
+ # surface nobody ships. Caught the first time it ran: the system Python had
114
+ # anthropic 0.122.0 while the file pinned 1.4.0, and the check passed —
115
+ # green about a package the sweep will not use.
116
+ for name, want in pinned.items():
117
+ try:
118
+ have = getattr(importlib.import_module(name), "__version__", None)
119
+ except ImportError:
120
+ continue # already reported above
121
+ if have and have != want:
122
+ problems.append(
123
+ f"{name}: requirements-eval.txt pins {want} but {have} is "
124
+ "installed — this check just read the wrong package"
125
+ )
126
+
127
+ if problems:
128
+ print(f"✗ {len(problems)} eval-SDK surface problem(s):\n")
129
+ for problem in problems:
130
+ print(f" - {problem}")
131
+ print("\nA bump that removes any of these breaks the sweep, not the CI.")
132
+ return 1
133
+
134
+ versions = ", ".join(
135
+ f"{n} {getattr(importlib.import_module(n), '__version__', '?')}" for n in REQUIRED
136
+ )
137
+ print(f"✓ eval SDK surface intact — {versions}")
138
+ return 0
139
+
140
+
141
+ if __name__ == "__main__":
142
+ sys.exit(main())