master-skill 0.10.1 → 0.12.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 (77) 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/GEMINI.md +1 -1
  5. package/README.md +84 -336
  6. package/README_EN.md +108 -321
  7. package/bin/cli.mjs +249 -9
  8. package/gemini-extension.json +1 -1
  9. package/hooks/session-start +68 -74
  10. package/hooks/session_start.py +152 -0
  11. package/package.json +6 -2
  12. package/prebuilt/{compare → compare-masters}/SKILL.md +21 -2
  13. package/prebuilt/master-ajahn-chah/meta.json +6 -0
  14. package/prebuilt/master-ajahn-chah/tests/fidelity.jsonl +6 -6
  15. package/prebuilt/master-atisha/tests/fidelity.jsonl +4 -4
  16. package/prebuilt/master-curriculum/SKILL.md +1 -1
  17. package/prebuilt/master-curriculum/references/tiantai.md +1 -1
  18. package/prebuilt/master-debate/SKILL.md +15 -3
  19. package/prebuilt/master-fazang/tests/fidelity.jsonl +2 -2
  20. package/prebuilt/master-help/SKILL.md +94 -0
  21. package/prebuilt/master-help/tests/fidelity.jsonl +10 -0
  22. package/prebuilt/master-huineng/tests/fidelity.jsonl +4 -4
  23. package/prebuilt/master-kumarajiva/meta.json +14 -3
  24. package/prebuilt/master-kumarajiva/tests/fidelity.jsonl +3 -3
  25. package/prebuilt/master-mahasi-sayadaw/tests/fidelity.jsonl +4 -4
  26. package/prebuilt/master-milarepa/tests/fidelity.jsonl +3 -3
  27. package/prebuilt/master-nagarjuna/meta.json +19 -4
  28. package/prebuilt/master-nagarjuna/tests/fidelity.jsonl +6 -6
  29. package/prebuilt/master-ouyi/meta.json +5 -0
  30. package/prebuilt/master-ouyi/references/teaching.md +3 -3
  31. package/prebuilt/master-ouyi/tests/fidelity.jsonl +3 -3
  32. package/prebuilt/master-tsongkhapa/meta.json +32 -5
  33. package/prebuilt/master-tsongkhapa/tests/fidelity.jsonl +2 -2
  34. package/prebuilt/master-xuanzang/tests/fidelity.jsonl +3 -3
  35. package/prebuilt/master-xuyun/tests/fidelity.jsonl +6 -6
  36. package/prebuilt/master-zhiyi/meta.json +2 -2
  37. package/prebuilt/master-zhiyi/tests/fidelity.jsonl +2 -2
  38. package/references/teaching-modes.md +8 -1
  39. package/routing.json +209 -0
  40. package/scripts/check-audit-ignores.py +105 -0
  41. package/scripts/check-eval-sdk-surface.py +142 -0
  42. package/scripts/check-gate-liveness.py +421 -0
  43. package/scripts/reaudit-report.py +163 -0
  44. package/scripts/regrade-report.py +157 -0
  45. package/scripts/smoke-eval-sdk.py +174 -0
  46. package/scripts/test-fidelity.py +992 -89
  47. package/scripts/validate-citation-references.py +150 -0
  48. package/scripts/validate-citation-templates.py +176 -0
  49. package/scripts/validate-fidelity.py +6 -1
  50. package/scripts/validate-fixture-terms.py +127 -0
  51. package/scripts/validate-routing.py +254 -0
  52. package/scripts/validate.py +63 -36
  53. package/scripts/verify-adjudication.py +316 -0
  54. package/scripts/verify_citations.py +739 -39
  55. package/skill-catalog.json +83 -20
  56. package/tools/cross_reference.py +44 -10
  57. package/tools/fojin-known-absent.json +14 -0
  58. package/tools/fojin_bridge.py +138 -8
  59. package/tools/rag_query.py +45 -2
  60. package/tools/skill_writer.py +50 -7
  61. package/tools/verify_sources.py +240 -15
  62. package/hooks/tests/test_run_hook.sh +0 -114
  63. package/hooks/tests/test_run_hook_cmd.sh +0 -94
  64. package/hooks/tests/test_session_start.sh +0 -149
  65. package/scripts/tests/test_check_manifest_versions.py +0 -217
  66. package/scripts/tests/test_debate_protocol.py +0 -159
  67. package/scripts/tests/test_injection_hardening.py +0 -174
  68. package/scripts/tests/test_select_fidelity_smoke.py +0 -142
  69. package/scripts/tests/test_validate_citation_contract.py +0 -408
  70. package/scripts/tests/test_validate_cross_critique.py +0 -149
  71. package/scripts/tests/test_validate_curriculum_sources.py +0 -144
  72. package/scripts/tests/test_validate_fidelity.py +0 -59
  73. package/scripts/tests/test_validate_lore_triggers_content.py +0 -372
  74. package/scripts/tests/test_validate_persona_fidelity.py +0 -317
  75. package/scripts/tests/test_validate_promptfoo_configs.py +0 -386
  76. package/scripts/tests/test_validate_workflow.py +0 -265
  77. /package/prebuilt/{compare → compare-masters}/tests/fidelity.jsonl +0 -0
@@ -8,8 +8,15 @@
8
8
  "citation_contract": {
9
9
  "version": 1,
10
10
  "claim_policy": "declared_sources_only",
11
- "required_for": ["doctrinal_claim", "practice_guidance", "text_interpretation"],
12
- "allowed_source_types": ["tibetan_canon", "tibetan_treatise"],
11
+ "required_for": [
12
+ "doctrinal_claim",
13
+ "practice_guidance",
14
+ "text_interpretation"
15
+ ],
16
+ "allowed_source_types": [
17
+ "tibetan_canon",
18
+ "tibetan_treatise"
19
+ ],
13
20
  "minimum_claim_coverage": 0.9,
14
21
  "live_retrieval_allowed": true
15
22
  },
@@ -17,7 +24,12 @@
17
24
  "school": "格鲁派 (dge lugs pa, '善规派') — 由其改革噶当派建立",
18
25
  "era": "1357-1419",
19
26
  "birthplace": "青海宗喀('宗喀巴'即'宗喀人'之意,今西宁附近)",
20
- "languages": ["bo", "sa", "zh", "en"],
27
+ "languages": [
28
+ "bo",
29
+ "sa",
30
+ "zh",
31
+ "en"
32
+ ],
21
33
  "fojin_entity_id": null,
22
34
  "sources": [
23
35
  {
@@ -26,6 +38,12 @@
26
38
  "title": "宗喀巴全集 (gsung 'bum / Collected Works)",
27
39
  "note": "BDRC.io 检索 'tsong kha pa' 获取实际 W-number 与卷次"
28
40
  },
41
+ {
42
+ "type": "tibetan_canon",
43
+ "id": "Toh:3861",
44
+ "title": "入中论 (Madhyamakāvatāra)",
45
+ "note": "月称造,应成中观根本论典;宗喀巴 SKILL.md §传承引用格式 (Toh 3861)"
46
+ },
29
47
  {
30
48
  "type": "tibetan_treatise",
31
49
  "id": "Lam-rim-chen-mo",
@@ -87,6 +105,7 @@
87
105
  "应成中观",
88
106
  "dbu ma thal 'gyur ba",
89
107
  "缘起性空",
108
+ "空性",
90
109
  "无自性",
91
110
  "ngo bo nyid med pa",
92
111
  "戒律",
@@ -125,7 +144,15 @@
125
144
  "monologue": "上堂依菩提道次第广论开列三士道,举出离心菩提心空正见为骨干,归甘丹耳传之道次第。"
126
145
  },
127
146
  "cross_critique": [
128
- { "target_master": "huineng", "position": "对禅宗顿悟:无次第直指乃极利根所行;中根以下若不依应成正理分别自相,离名言假施设即立自性见,反成执碍。", "citation": "Lam-rim-chen-mo" },
129
- { "target_master": "ouyi", "position": "对天台净土合修:教观判摄虽善,然空性正见须用应成因明遮遣自相;若未破自性见,所行虽多终为有漏。", "citation": "Drang-nges-legs-bshad-snying-po" }
147
+ {
148
+ "target_master": "huineng",
149
+ "position": "对禅宗顿悟:无次第直指乃极利根所行;中根以下若不依应成正理分别自相,离名言假施设即立自性见,反成执碍。",
150
+ "citation": "Lam-rim-chen-mo"
151
+ },
152
+ {
153
+ "target_master": "ouyi",
154
+ "position": "对天台净土合修:教观判摄虽善,然空性正见须用应成因明遮遣自相;若未破自性见,所行虽多终为有漏。",
155
+ "citation": "Drang-nges-legs-bshad-snying-po"
156
+ }
130
157
  ]
131
158
  }
@@ -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"}
@@ -2,6 +2,11 @@
2
2
 
3
3
  > **何时读这个**:用户犹豫该用 `/compare-masters` / `/master-debate` / `/master-curriculum` 哪个时;用户问"哪些祖师对这个问题怎么看"但意图不明时。
4
4
 
5
+ > **这棵树现在有可执行版本。** 判定顺序与关键词已固化到仓库根的 `routing.json`,
6
+ > 由 `master-skill recommend "<用户原话>"`(确定性)和 `/master-help`(聊天内)共用。
7
+ > 本文下方"示例输入分类"的 10 条即 `tests/cli.test.mjs` 的回归 fixture——
8
+ > **改这里的表就会让测试失败**,这是刻意的:散文与实现不允许各走各的。
9
+
5
10
  ## 三模式速查
6
11
 
7
12
  | 模式 | 维度 | 轮次 | 输出 | 适合场景 |
@@ -50,9 +55,11 @@
50
55
 
51
56
  ### `/master-curriculum` 触发关键词
52
57
 
53
- - 学修次第 / 学习计划 / 入门 / 先学什么 / 从哪开始 / 路径推荐 / curriculum / roadmap / 怎么修
58
+ - 学修次第 / 学习计划 / 入门 / 先学什么 / 从哪开始 / 开始学 / 应该读 / 路径推荐 / 按什么顺序 / curriculum / roadmap
54
59
  - 问"禅宗从哪开始学" → 时序路径,不是比较
55
60
 
61
+ > ⚠️ `怎么修` **不是**本模式的触发词。它是求法,不是求路径——"这个怎么修"归单 master skill(见本文决策树末端),"从哪开始学"才归这里。本文此处曾同时把 `怎么修` 列为 curriculum 触发词又在决策树里判给单 master,两处矛盾已按后者裁定。
62
+
56
63
  ## 示例输入分类
57
64
 
58
65
  | 用户原话 | 模式 | 理由 |
package/routing.json ADDED
@@ -0,0 +1,209 @@
1
+ {
2
+ "version": 1,
3
+ "_comment": "Machine-readable routing table. Persona keywords are NOT duplicated here — they live in each prebuilt/<slug>/meta.json search_scope.keywords and are read at match time. This file holds only what has no other machine-readable home: teaching-mode short-circuit order, the topic->pairing fallback, and the scoring constants. scripts/validate-routing.py enforces that every keyword set is pairwise disjoint (no string appears twice, and no string is a substring of another row's string), so a match can never depend on iteration order.",
4
+ "weights": {
5
+ "keyword_hit": 3
6
+ },
7
+ "_min_keyword_length_comment": "Persona keywords shorter than this are ignored when scoring. Seven single-character doctrinal atoms (空 戒 定 慧 苦 禅 业) live in meta.json search_scope.keywords, and matching by containment made them fire on ordinary Chinese: 「有空吗」 scored Kumārajīva and Nāgārjuna, 「我戒烟了」 scored Buddhaghosa. Every one of them also appears inside a longer keyword (空性 / 持戒 / 戒定慧 / 毕竟空 …), so dropping them costs no real recall; queries that only carry the bare atom fall through to topic_pairings, which is what that layer is for.",
8
+ "min_keyword_length": 2,
9
+ "mode_rules": [
10
+ {
11
+ "mode": "master-curriculum",
12
+ "order": 1,
13
+ "note": "纵向 / 时序:请求学修路径,不是比较",
14
+ "keywords": [
15
+ "学习计划",
16
+ "学修次第",
17
+ "入门",
18
+ "先学什么",
19
+ "从哪开始",
20
+ "开始学",
21
+ "应该读",
22
+ "下一步读什么",
23
+ "路径推荐",
24
+ "按什么顺序",
25
+ "curriculum",
26
+ "roadmap"
27
+ ]
28
+ },
29
+ {
30
+ "mode": "master-debate",
31
+ "order": 2,
32
+ "note": "横向 / 对立:4 轮立论-反驳-回应-综合",
33
+ "keywords": [
34
+ "辩论",
35
+ "各执一词",
36
+ "谁更对",
37
+ "高下",
38
+ "之争",
39
+ "之辩",
40
+ "分判",
41
+ "debate"
42
+ ]
43
+ },
44
+ {
45
+ "mode": "compare-masters",
46
+ "order": 3,
47
+ "note": "横向 / 并列:单轮多祖师平铺对比",
48
+ "keywords": [
49
+ "对比",
50
+ "比较",
51
+ "不同",
52
+ "各派怎么看",
53
+ "各位祖师",
54
+ "多个角度",
55
+ "异同",
56
+ "compare"
57
+ ]
58
+ }
59
+ ],
60
+ "_situations_comment": "Vernacular felt-state routing, consulted after persona keyword scoring and before topic_pairings. search_scope.keywords are doctrinal retrieval terms; a beginner does not type 四念处, they type 坐不住. Empirically, 4 of the 11 rows in the README 「你的状况」 table resolved to default_pairing because no doctrinal keyword matched — those 4 are the rows below. The other 7 already route correctly off persona keywords and are deliberately NOT duplicated here: this layer holds only what the keyword layer cannot reach.",
61
+ "situations": [
62
+ {
63
+ "id": "scattered-mind",
64
+ "keywords": ["妄念", "杂念", "坐不住", "静不下来", "定不下来", "心乱"],
65
+ "masters": ["master-xuyun", "master-zhiyi", "master-ajahn-chah"],
66
+ "note": "参话头 / 止观 / 正念观察"
67
+ },
68
+ {
69
+ "id": "texts-opaque",
70
+ "keywords": ["看不懂", "读不懂", "理不清", "没有逻辑"],
71
+ "masters": ["master-xuanzang"],
72
+ "note": "唯识严密分析"
73
+ },
74
+ {
75
+ "id": "no-traction",
76
+ "keywords": ["无力感", "使不上力", "没有进步", "学佛很久", "提不起劲"],
77
+ "masters": ["master-yinguang"],
78
+ "note": "老实念佛"
79
+ },
80
+ {
81
+ "id": "plainest-practice",
82
+ "keywords": ["最朴素", "朴素", "最简单的修法"],
83
+ "masters": ["master-ajahn-chah"],
84
+ "note": "南传森林禅 · 出入息念"
85
+ }
86
+ ],
87
+ "topic_pairings": [
88
+ {
89
+ "id": "pureland",
90
+ "keywords": ["念佛", "往生", "净土"],
91
+ "masters": ["master-yinguang", "master-ouyi"],
92
+ "note": "净土专精 + 跨宗派"
93
+ },
94
+ {
95
+ "id": "chan-practice",
96
+ "keywords": ["参禅", "话头", "开悟"],
97
+ "masters": ["master-huineng", "master-xuyun"],
98
+ "note": "古今禅宗对比"
99
+ },
100
+ {
101
+ "id": "yogacara-vs-madhyamaka",
102
+ "keywords": ["唯识", "空有", "性相", "法相"],
103
+ "masters": ["master-xuanzang", "master-kumarajiva"],
104
+ "note": "唯识 vs 中观"
105
+ },
106
+ {
107
+ "id": "panjiao",
108
+ "keywords": ["判教", "圆融", "止观"],
109
+ "masters": ["master-zhiyi", "master-fazang"],
110
+ "note": "天台 vs 华严"
111
+ },
112
+ {
113
+ "id": "integrated-path",
114
+ "keywords": ["修行次第", "综合法门"],
115
+ "masters": ["master-ouyi", "master-yinguang"],
116
+ "note": "综合 vs 专修"
117
+ },
118
+ {
119
+ "id": "vinaya",
120
+ "keywords": ["戒律", "持戒", "律仪", "行持"],
121
+ "masters": ["master-xuyun", "master-atisha", "master-buddhaghosa"],
122
+ "note": "汉传禅门律 vs 印藏菩萨律 vs 上座部律藏注释(合并原「戒律/行持/日常」与「戒律/持戒/律仪」两行,去除键冲突)"
123
+ },
124
+ {
125
+ "id": "emptiness",
126
+ "keywords": ["般若", "空性", "中观", "缘起性空", "应成", "毕竟空"],
127
+ "masters": ["master-kumarajiva", "master-tsongkhapa", "master-huineng"],
128
+ "note": "早期中观译师 · 应成中观精确分判 · 禅宗直指(合并原「般若/空性」与「中观/缘起性空/应成」两行)"
129
+ },
130
+ {
131
+ "id": "lamrim",
132
+ "keywords": ["道次第", "三士道", "下士道", "中士道", "上士道", "lam rim"],
133
+ "masters": ["master-atisha", "master-tsongkhapa"],
134
+ "note": "印藏桥梁源头 vs 格鲁派系统化"
135
+ },
136
+ {
137
+ "id": "consciousness",
138
+ "keywords": ["心识", "阿赖耶", "心所", "末那"],
139
+ "masters": ["master-xuanzang", "master-buddhaghosa", "master-huineng"],
140
+ "note": "唯识 vs 上座部阿毗达摩 vs 禅宗直指"
141
+ },
142
+ {
143
+ "id": "ascetic",
144
+ "keywords": ["苦行", "闭关", "山中修行", "头陀"],
145
+ "masters": ["master-xuyun", "master-milarepa"],
146
+ "note": "汉传禅门头陀 vs 藏传瑜伽士传统"
147
+ },
148
+ {
149
+ "id": "mindfulness",
150
+ "keywords": ["正念", "观心", "觉知"],
151
+ "masters": ["master-huineng", "master-ajahn-chah", "master-mahasi-sayadaw"],
152
+ "note": "禅宗自性 vs 南传 sati 朴素 vs 缅甸标记法"
153
+ },
154
+ {
155
+ "id": "meditation-objects",
156
+ "keywords": ["禅修方法", "业处", "所缘"],
157
+ "masters": ["master-buddhaghosa", "master-mahasi-sayadaw", "master-ajahn-chah"],
158
+ "note": "论藏四十种业处 vs 标记法腹部起伏 vs 朴素观心"
159
+ },
160
+ {
161
+ "id": "insight-stages",
162
+ "keywords": ["七清净", "十六观智", "观智"],
163
+ "masters": ["master-buddhaghosa", "master-mahasi-sayadaw"],
164
+ "note": "《清净道论》原典 vs 现代缅甸应用(原行含「道次第」,已移除以避免与 lamrim 冲突)"
165
+ },
166
+ {
167
+ "id": "renunciation",
168
+ "keywords": ["出离心", "暇满", "无常"],
169
+ "masters": ["master-yinguang", "master-atisha", "master-ajahn-chah"],
170
+ "note": "净土 · 噶当 · 上座部跨传统出离观对比"
171
+ },
172
+ {
173
+ "id": "bodhicitta",
174
+ "keywords": ["菩提心", "慈悲", "自他相换"],
175
+ "masters": ["master-atisha", "master-ouyi"],
176
+ "note": "印藏自他相换 vs 跨宗派融通"
177
+ },
178
+ {
179
+ "id": "teacher",
180
+ "keywords": ["上师", "善知识", "依止"],
181
+ "masters": ["master-xuyun", "master-atisha", "master-tsongkhapa"],
182
+ "note": "汉传善知识 vs 噶当依止论 vs 格鲁视师如佛"
183
+ },
184
+ {
185
+ "id": "scholastic",
186
+ "keywords": ["论师风格", "经院严密", "因明"],
187
+ "masters": ["master-xuanzang", "master-tsongkhapa", "master-buddhaghosa"],
188
+ "note": "唯识 · 应成中观 · 上座部三大论师传统"
189
+ },
190
+ {
191
+ "id": "four-traditions",
192
+ "keywords": ["四大传统", "四方对照"],
193
+ "masters": [
194
+ "master-nagarjuna",
195
+ "master-huineng",
196
+ "master-tsongkhapa",
197
+ "master-buddhaghosa"
198
+ ],
199
+ "note": "印度中观 · 禅 · 应成中观 · 上座部论藏,四方系统对照"
200
+ },
201
+ {
202
+ "id": "cross-tradition-meditation",
203
+ "keywords": ["跨传统禅修", "大手印"],
204
+ "masters": ["master-huineng", "master-milarepa", "master-ajahn-chah"],
205
+ "note": "禅 · 大手印 · 森林禅,跨传统禅修法"
206
+ }
207
+ ],
208
+ "default_pairing": ["master-kumarajiva", "master-yinguang"]
209
+ }
@@ -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())