master-skill 0.10.1 → 0.11.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 (35) 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 +52 -297
  6. package/README_EN.md +52 -278
  7. package/bin/cli.mjs +237 -2
  8. package/gemini-extension.json +1 -1
  9. package/hooks/session-start +4 -1
  10. package/package.json +3 -2
  11. package/prebuilt/master-curriculum/SKILL.md +1 -1
  12. package/prebuilt/master-debate/SKILL.md +1 -1
  13. package/prebuilt/master-help/SKILL.md +86 -0
  14. package/prebuilt/master-help/tests/fidelity.jsonl +10 -0
  15. package/prebuilt/master-kumarajiva/meta.json +14 -3
  16. package/prebuilt/master-nagarjuna/meta.json +19 -4
  17. package/prebuilt/master-tsongkhapa/meta.json +26 -5
  18. package/references/teaching-modes.md +8 -1
  19. package/routing.json +209 -0
  20. package/scripts/check-gate-liveness.py +222 -0
  21. package/scripts/test-fidelity.py +320 -49
  22. package/scripts/tests/test_check_gate_liveness.py +232 -0
  23. package/scripts/tests/test_check_response.py +190 -0
  24. package/scripts/tests/test_fidelity_providers.py +202 -0
  25. package/scripts/tests/test_select_fidelity_smoke.py +2 -2
  26. package/scripts/tests/test_validate.py +145 -0
  27. package/scripts/tests/test_validate_citation_contract.py +1 -1
  28. package/scripts/tests/test_validate_fidelity.py +2 -2
  29. package/scripts/tests/test_validate_workflow.py +21 -2
  30. package/scripts/validate-fidelity.py +6 -1
  31. package/scripts/validate-routing.py +254 -0
  32. package/scripts/validate.py +63 -36
  33. package/skill-catalog.json +83 -20
  34. /package/prebuilt/{compare → compare-masters}/SKILL.md +0 -0
  35. /package/prebuilt/{compare → compare-masters}/tests/fidelity.jsonl +0 -0
package/bin/cli.mjs CHANGED
@@ -11,6 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
11
  const PACKAGE_ROOT = path.join(__dirname, "..");
12
12
  const PREBUILT = path.join(PACKAGE_ROOT, "prebuilt");
13
13
  const CATALOG_PATH = path.join(PACKAGE_ROOT, "skill-catalog.json");
14
+ const ROUTING_PATH = path.join(PACKAGE_ROOT, "routing.json");
14
15
  const SKILLS_DIR = path.join(os.homedir(), ".claude", "skills");
15
16
  const SKILL_KINDS = new Set(["persona", "teaching-mode", "generator"]);
16
17
 
@@ -234,7 +235,7 @@ function availableMasters() {
234
235
  if (!fs.existsSync(PREBUILT)) return [];
235
236
  return fs
236
237
  .readdirSync(PREBUILT, { withFileTypes: true })
237
- .filter((d) => d.isDirectory() && d.name !== "compare")
238
+ .filter((d) => d.isDirectory() && d.name !== "compare-masters")
238
239
  .map((d) => {
239
240
  const skillMd = path.join(PREBUILT, d.name, "SKILL.md");
240
241
  const fm = fs.existsSync(skillMd) ? parseFrontmatter(skillMd) : {};
@@ -564,18 +565,246 @@ function cmdInspect(name, { json = false } = {}) {
564
565
  return 0;
565
566
  }
566
567
 
568
+ // --- recommend ---
569
+ //
570
+ // Routing used to exist only as prose (a weighted-match paragraph and a
571
+ // pairing table in prebuilt/compare-masters/SKILL.md, a decision tree in
572
+ // references/teaching-modes.md), so nothing could execute or test it. This
573
+ // reads routing.json for the parts that had no machine-readable home and
574
+ // scores personas straight off each meta.json search_scope.keywords, which
575
+ // stays the single source of truth for keywords.
576
+ //
577
+ // Only exact keyword containment scores. The prose also described "related
578
+ // match = 2" and "weak match = 1" tiers, but those need a synonym/domain
579
+ // map that does not exist — implementing them would dress a guess up as an
580
+ // algorithm. Ties break on tradition diversity, then slug order, so the
581
+ // same query always yields the same answer.
582
+
583
+ function loadRouting() {
584
+ const routing = JSON.parse(fs.readFileSync(ROUTING_PATH, "utf8"));
585
+ if (routing?.version !== 1) {
586
+ throw new Error("Invalid routing table: version must be 1");
587
+ }
588
+ return routing;
589
+ }
590
+
591
+ function personaCandidates() {
592
+ return CATALOG.skills
593
+ .filter((skill) => skill.kind === "persona")
594
+ .map((skill) => {
595
+ const metaPath = path.join(PACKAGE_ROOT, skill.source, "meta.json");
596
+ const meta = fs.existsSync(metaPath) ? readJson(metaPath) : {};
597
+ return {
598
+ name: skill.name,
599
+ tradition: meta.tradition || "(unspecified)",
600
+ keywords: meta.search_scope?.keywords || [],
601
+ };
602
+ });
603
+ }
604
+
605
+ // Greedy pick: highest score first, then prefer a tradition not yet chosen
606
+ // so the result shows plural perspectives rather than three Chan masters.
607
+ function pickDiverse(scored, limit) {
608
+ const pool = [...scored];
609
+ const chosen = [];
610
+ const seenTraditions = new Set();
611
+ while (pool.length && chosen.length < limit) {
612
+ let idx = pool.findIndex((c) => !seenTraditions.has(c.tradition));
613
+ if (idx === -1) idx = 0;
614
+ const [pick] = pool.splice(idx, 1);
615
+ chosen.push(pick);
616
+ seenTraditions.add(pick.tradition);
617
+ }
618
+ return chosen;
619
+ }
620
+
621
+ // Shared by the situations and topic_pairings layers. Keyword sets are
622
+ // pairwise disjoint within each section (enforced by validate-routing.py),
623
+ // but a query can still touch two rows through different keywords — so the
624
+ // tiebreak is explicit and total: most hits, then longest single hit, then
625
+ // row id, which leaves no room for iteration order to decide.
626
+ function pickRow(rows, hitsFor) {
627
+ return (
628
+ rows
629
+ .map((row) => ({ row, matched: hitsFor(row.keywords) }))
630
+ .filter((entry) => entry.matched.length)
631
+ .sort(
632
+ (a, b) =>
633
+ b.matched.length - a.matched.length ||
634
+ Math.max(...b.matched.map((k) => k.length)) -
635
+ Math.max(...a.matched.map((k) => k.length)) ||
636
+ a.row.id.localeCompare(b.row.id)
637
+ )[0] || null
638
+ );
639
+ }
640
+
641
+ function expandSlugs(slugs) {
642
+ const byName = new Map(personaCandidates().map((c) => [c.name, c]));
643
+ return slugs.map((name) => ({
644
+ name,
645
+ command: `/${name}`,
646
+ tradition: byName.get(name)?.tradition || "(unspecified)",
647
+ score: 0,
648
+ matched: [],
649
+ }));
650
+ }
651
+
652
+ function recommendData(query) {
653
+ const routing = loadRouting();
654
+ const q = String(query).toLowerCase();
655
+ const hitsFor = (keywords) =>
656
+ keywords.filter((kw) => q.includes(String(kw).toLowerCase()));
657
+
658
+ // Priority 1 — teaching mode, short-circuited in declared order.
659
+ for (const rule of [...routing.mode_rules].sort((a, b) => a.order - b.order)) {
660
+ const matched = hitsFor(rule.keywords);
661
+ if (matched.length) {
662
+ return {
663
+ query,
664
+ resolvedBy: "mode_rules",
665
+ kind: "teaching-mode",
666
+ mode: rule.mode,
667
+ command: `/${rule.mode}`,
668
+ matched,
669
+ note: rule.note || null,
670
+ masters: [],
671
+ };
672
+ }
673
+ }
674
+
675
+ // Priority 2 — score personas off their own declared keywords. Keywords
676
+ // below min_keyword_length are skipped: see the note in routing.json.
677
+ const weight = routing.weights?.keyword_hit ?? 3;
678
+ const minLen = routing.min_keyword_length ?? 2;
679
+ const scored = personaCandidates()
680
+ .map((c) => {
681
+ const matched = hitsFor(c.keywords.filter((kw) => String(kw).length >= minLen));
682
+ return { ...c, matched, score: matched.length * weight };
683
+ })
684
+ .filter((c) => c.score > 0)
685
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
686
+
687
+ if (scored.length) {
688
+ return {
689
+ query,
690
+ resolvedBy: "persona_keywords",
691
+ kind: "persona",
692
+ mode: null,
693
+ command: null,
694
+ matched: [],
695
+ note: null,
696
+ masters: pickDiverse(scored, 3).map((c) => ({
697
+ name: c.name,
698
+ command: `/${c.name}`,
699
+ tradition: c.tradition,
700
+ score: c.score,
701
+ matched: c.matched,
702
+ })),
703
+ };
704
+ }
705
+
706
+ // Priority 3 — vernacular felt-state. A beginner types 坐不住, not 四念处,
707
+ // and search_scope.keywords carry no such wording, so these queries used to
708
+ // land on the default pairing. Placed after keyword scoring (an explicit
709
+ // doctrinal term is a stronger signal) and before topic_pairings (which was
710
+ // authored to pair masters for /compare-masters, not to answer "ask who?").
711
+ const situation = pickRow(routing.situations || [], hitsFor);
712
+ if (situation) {
713
+ return {
714
+ query,
715
+ resolvedBy: "situations",
716
+ kind: "persona",
717
+ mode: null,
718
+ command: null,
719
+ matched: situation.matched,
720
+ note: situation.row.note || null,
721
+ masters: expandSlugs(situation.row.masters),
722
+ };
723
+ }
724
+
725
+ // Priority 4 — topic pairing fallback.
726
+ const pairing = pickRow(routing.topic_pairings, hitsFor);
727
+ if (pairing) {
728
+ return {
729
+ query,
730
+ resolvedBy: "topic_pairings",
731
+ kind: "persona",
732
+ mode: null,
733
+ command: null,
734
+ matched: pairing.matched,
735
+ note: pairing.row.note || null,
736
+ masters: expandSlugs(pairing.row.masters),
737
+ };
738
+ }
739
+
740
+ // Priority 5 — nothing matched at all.
741
+ return {
742
+ query,
743
+ resolvedBy: "default_pairing",
744
+ kind: "persona",
745
+ mode: null,
746
+ command: null,
747
+ matched: [],
748
+ note: "无关键词命中,回退到默认配对",
749
+ masters: expandSlugs(routing.default_pairing),
750
+ };
751
+ }
752
+
753
+ function cmdRecommend(query, { json = false } = {}) {
754
+ if (!query || !String(query).trim()) {
755
+ console.log('Usage: master-skill recommend "<你的问题或状况>"');
756
+ return 1;
757
+ }
758
+
759
+ const data = recommendData(query);
760
+
761
+ if (json) {
762
+ printJson(data);
763
+ return 0;
764
+ }
765
+
766
+ if (data.kind === "teaching-mode") {
767
+ console.log(`\n建议使用教学模式:${data.command}`);
768
+ if (data.note) console.log(` ${data.note}`);
769
+ console.log(` 命中关键词:${data.matched.join("、")}`);
770
+ console.log(
771
+ `\n(若只想听一位祖师,直接用对应的 /master-<name>;` +
772
+ `master-skill list 可列出全部。)\n`
773
+ );
774
+ return 0;
775
+ }
776
+
777
+ console.log(`\n推荐祖师:`);
778
+ for (const m of data.masters) {
779
+ const why = m.matched.length
780
+ ? `命中 ${m.matched.slice(0, 5).join("、")}`
781
+ : data.note || "主题配对";
782
+ console.log(` ${m.command} [${m.tradition}] ${why}`);
783
+ }
784
+ if (data.resolvedBy === "default_pairing") {
785
+ console.log(`\n 没有明确命中,给的是通用入门配对。`);
786
+ }
787
+ console.log(
788
+ `\n(想看多位祖师并列 → /compare-masters;想看对辩 → /master-debate;` +
789
+ `想要学修路径 → /master-curriculum)\n`
790
+ );
791
+ return 0;
792
+ }
793
+
567
794
  function showHelp() {
568
795
  console.log(`
569
796
  master-skill v${pkgVersion()} — Buddhist Master AI Skills installer
570
797
 
571
798
  Usage:
572
799
  master-skill install <name...> Install skills to ~/.claude/skills/
573
- master-skill install --all Install all 19 available skills
800
+ master-skill install --all Install all ${CATALOG.skills.length} available skills
574
801
  master-skill update --all Reinstall all skills, clearing stale files
575
802
  master-skill list List available skills
576
803
  master-skill list --json Print available skills as JSON
577
804
  master-skill inspect <name> Show source/runtime metadata for one master
578
805
  master-skill inspect <name> --json
806
+ master-skill recommend "<问题>" Suggest which master or teaching mode to use
807
+ master-skill recommend "<问题>" --json
579
808
  master-skill doctor Check local install and runtime paths
580
809
  master-skill doctor --json Print diagnostics as JSON
581
810
  master-skill uninstall <name...> Remove installed skills
@@ -594,6 +823,8 @@ Examples:
594
823
  npx master-skill update --all
595
824
  npx master-skill list
596
825
  npx master-skill inspect huineng
826
+ npx master-skill recommend "念佛怎么念才算老实"
827
+ npx master-skill recommend "禅宗从哪开始学"
597
828
  npx master-skill doctor
598
829
  npx master-skill uninstall zhiyi
599
830
  `);
@@ -617,6 +848,10 @@ if (CATALOG) {
617
848
  if (cmdDoctor({ json }) > 0) process.exitCode = 1;
618
849
  } else if (cmd === "inspect") {
619
850
  if (cmdInspect(positionalArgs[1], { json }) > 0) process.exitCode = 1;
851
+ } else if (cmd === "recommend") {
852
+ // Join the rest so an unquoted multi-word query still works.
853
+ const query = positionalArgs.slice(1).join(" ");
854
+ if (cmdRecommend(query, { json }) > 0) process.exitCode = 1;
620
855
  } else if (cmd === "install") {
621
856
  const rest = positionalArgs.slice(1);
622
857
  if (rest.includes("--all")) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "master-skill",
3
3
  "description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传.",
4
- "version": "0.10.1",
4
+ "version": "0.11.0",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -64,7 +64,10 @@ done
64
64
 
65
65
  # Build the context message
66
66
  CONTEXT="Master-skill plugin loaded. Available Buddhist masters:
67
- ${MASTERS_LIST} /compare-mastersmulti-tradition comparison
67
+ ${MASTERS_LIST} /master-helpnot sure which master or mode? start here
68
+ /compare-masters — multi-tradition comparison
69
+ /master-debate — 4-round adversarial dialectic between masters
70
+ /master-curriculum — staged learning path within a tradition
68
71
  /create-master — generate new master from FoJin knowledge graph
69
72
 
70
73
  All doctrinal responses include CBETA citations linked to fojin.app."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "master-skill",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 pre-built masters across 印度 / 汉传 / 藏传 / 南传, plus /compare-masters, /master-debate, and /master-curriculum.",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "validate:versions": "python3 scripts/check-manifest-versions.py",
16
16
  "test:hook": "bash hooks/tests/test_session_start.sh && bash hooks/tests/test_run_hook.sh && bash hooks/tests/test_run_hook_cmd.sh",
17
17
  "test:cli": "node --test tests/cli.test.mjs",
18
- "test": "python3 scripts/validate.py --strict && python3 scripts/validate-fidelity.py && python3 scripts/validate-persona-fidelity.py && python3 scripts/check-manifest-versions.py && python3 scripts/test-fidelity.py --all --dry-run && node --test tests/cli.test.mjs",
18
+ "test": "python3 scripts/check-gate-liveness.py && python3 scripts/validate.py --strict && python3 scripts/validate-fidelity.py && python3 scripts/validate-persona-fidelity.py && python3 scripts/check-manifest-versions.py && python3 scripts/validate-routing.py && python3 scripts/test-fidelity.py --all --dry-run && node --test tests/cli.test.mjs",
19
19
  "test:smoke": "python3 scripts/test-fidelity.py --master yinguang --max-tests 1",
20
20
  "prepack": "node bin/cli.mjs list"
21
21
  },
@@ -71,6 +71,7 @@
71
71
  "gemini-extension.json",
72
72
  "GEMINI.md",
73
73
  "skill-catalog.json",
74
+ "routing.json",
74
75
  "SKILL.md",
75
76
  "tools/",
76
77
  "prompts/",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: master-curriculum
3
- description: Use when user asks for a sequenced learning path within a Buddhist tradition — 学修次第, 先学什么, 从哪入门, 下一步读什么, curriculum, 学习计划, 路径推荐. Differs from /compare-masters (parallel opinion) and /master-debate (adversarial dialectic) by being 纵向 / 时序: stage-by-stage plan keyed on tradition × level (L0-L3) → foundation → intermediate → advanced + blind spots. Trigger is planning intent — "禅宗对比" goes to /compare-masters; "禅宗从哪开始学" goes here.
3
+ description: 'Use when user asks for a sequenced learning path within a Buddhist tradition — 学修次第, 先学什么, 从哪入门, 下一步读什么, curriculum, 学习计划, 路径推荐. Differs from /compare-masters (parallel opinion) and /master-debate (adversarial dialectic) by being 纵向 / 时序: stage-by-stage plan keyed on tradition × level (L0-L3) → foundation → intermediate → advanced + blind spots. Trigger is planning intent — "禅宗对比" goes to /compare-masters; "禅宗从哪开始学" goes here.'
4
4
  version: 0.7.0
5
5
  license: MIT
6
6
  kind: meta-skill
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: master-debate
3
- description: Use when user explicitly asks for an adversarial / multi-round dialectic between masters — 祖师辩论, 各执一词, 谁更对, debate, 应成 vs 顿悟, 顿渐之争. Differs from /compare-masters (parallel single-round) by being adversarial multi-round via fresh-subagent orchestration. Topics 空有 / 禅净 / 性相 / 戒律 vs 内观 — trigger is adversarial framing: "禅净比较" → compare; "禅净辩论 / 谁更究竟" → here.
3
+ description: 'Use when user explicitly asks for an adversarial / multi-round dialectic between masters — 祖师辩论, 各执一词, 谁更对, debate, 应成 vs 顿悟, 顿渐之争. Differs from /compare-masters (parallel single-round) by being adversarial multi-round via fresh-subagent orchestration. Topics 空有 / 禅净 / 性相 / 戒律 vs 内观 — trigger is adversarial framing: "禅净比较" → compare; "禅净辩论 / 谁更究竟" → here.'
4
4
  version: 0.8.0
5
5
  license: MIT
6
6
  kind: meta-skill
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: master-help
3
+ description: 'Use ONLY when the user says they do not know which master or which teaching mode to use — 不知道问谁, 该找哪位祖师, 该用哪个模式, 有哪些法师, which master should I ask, help me choose. This is a router, not a teacher: it names a destination and stops. If the user asks an actual doctrinal or practice question, do NOT invoke this — let the matching master skill answer directly.'
4
+ version: 0.11.0
5
+ license: MIT
6
+ kind: meta-skill
7
+ verified_by: xr843
8
+ verified_at: 2026-07-20
9
+ ---
10
+
11
+ # 该问谁 (Master Help) — 路由 Skill
12
+
13
+ > 本 skill 只做导航,不讲教义。选定目标后立即交棒,不要代替祖师回答。
14
+
15
+ ## 唯一职责
16
+
17
+ 用户不知道该用哪位祖师 / 哪个教学模式时,给出目标并停手。
18
+
19
+ **不要**在这里解释教理、给修行建议或引用经文——那是各 master skill 的职责,它们各自带着 `citation_contract` 和 HARD-GATE,本 skill 没有。
20
+
21
+ ## 数据源
22
+
23
+ 路由表在仓库根的 `routing.json`(`mode_rules` / `topic_pairings` / `default_pairing`),
24
+ 祖师关键词在各 `prebuilt/<slug>/meta.json` 的 `search_scope.keywords`。
25
+ 两处都是机器可读的单一数据源——**不要凭记忆列举祖师或关键词**,读文件。
26
+
27
+ 确定性实现同样可用:
28
+
29
+ ```
30
+ master-skill recommend "<用户原话>" --json
31
+ ```
32
+
33
+ 能跑就跑它,把结果转述给用户;跑不了再按下面的顺序人工走一遍。
34
+
35
+ ## 路由顺序(短路,不可乱序)
36
+
37
+ 与 `routing.json.mode_rules` 的 `order` 一致:
38
+
39
+ ```
40
+ 1. 命中「学习计划 / 入门 / 先学什么 / 从哪开始 / 按什么顺序」 → /master-curriculum
41
+ 2. 命中「辩论 / 谁更对 / 高下 / 之争 / 之辩 / 分判」 → /master-debate
42
+ 3. 命中「对比 / 比较 / 不同 / 异同 / 各派怎么看」 → /compare-masters
43
+ 4. 都不命中 → 单位祖师:按 meta.json search_scope.keywords 打分
44
+ 5. 仍无命中 → routing.json.situations 白话状况层
45
+ 6. 仍无命中 → routing.json.topic_pairings 主题配对
46
+ 7. 再无命中 → routing.json.default_pairing
47
+ ```
48
+
49
+ 第 5 步是给**说不出术语的人**用的。`search_scope.keywords` 是教理检索词,
50
+ 新手不会打"四念处",他会打"坐不住"。用户描述的是**感受**(妄念 / 看不懂 /
51
+ 无力感 / 想学最朴素的)而非**主题**时,走这一层。
52
+
53
+ 第 4 步打分规则:关键词**长度 ≥ 2** 才计分(单字 `空` `戒` `定` `慧` `苦` `禅` `业`
54
+ 会在日常汉语里误命中,已被 `min_keyword_length` 排除);命中数高者优先;
55
+ 平局时**优先不同传统**,仍平局按 slug 字典序。最多 3 位。
56
+
57
+ ## 输出格式
58
+
59
+ ```
60
+ 你的问题看起来是 {判断},建议:
61
+
62
+ /{目标} — {一句话理由}
63
+
64
+ (其他可选:{备选1}、{备选2})
65
+ ```
66
+
67
+ 三行以内说完。用户要的是入口,不是综述。
68
+
69
+ ## 边界
70
+
71
+ - 用户已经说清楚要问谁时,**不要**触发本 skill,直接让目标 skill 接手
72
+ - 不评价祖师高下,不说"某位更究竟"——这条与 `/compare-masters` 的 HARD-GATE 一致
73
+ - 推荐落到密法相关祖师(atisha / tsongkhapa / milarepa)时,照常交棒,
74
+ 由目标 skill 自己的边界规则处理密法内容
75
+ - 路由结果不确定时,宁可给 2 个候选让用户选,也不要猜死一个
76
+
77
+ ## Quick Reference — 15 位祖师按传统
78
+
79
+ | 传统 | 祖师 |
80
+ |------|------|
81
+ | 印度 | master-nagarjuna |
82
+ | 汉传 | master-kumarajiva · master-zhiyi · master-fazang · master-xuanzang · master-huineng · master-yinguang · master-ouyi · master-xuyun |
83
+ | 藏传 | master-atisha · master-tsongkhapa · master-milarepa |
84
+ | 南传 | master-buddhaghosa · master-mahasi-sayadaw · master-ajahn-chah |
85
+
86
+ > 此表仅供快速定位。判断该选谁时以 `routing.json` 与各 `meta.json` 为准。
@@ -0,0 +1,10 @@
1
+ {"q": "我想学佛但完全不知道该问哪位祖师", "must_mention": ["/master"], "must_recommend_existing_master": true, "difficulty": "basic"}
2
+ {"q": "禅宗从哪开始学", "must_mention": ["/master-curriculum"], "must_recommend_existing_master": true, "difficulty": "basic"}
3
+ {"q": "天台和华严的圆教有什么不同", "must_mention": ["/compare-masters"], "difficulty": "basic"}
4
+ {"q": "禅净之争究竟谁更对", "must_mention": ["/master-debate"], "difficulty": "basic"}
5
+ {"q": "阿赖耶识是什么意思,该请教谁", "must_mention": ["/master-xuanzang"], "must_recommend_existing_master": true, "difficulty": "intermediate"}
6
+ {"q": "有哪些法师可以问", "must_mention": ["印度", "汉传", "藏传", "南传"], "must_recommend_existing_master": true, "difficulty": "basic"}
7
+ {"q": "十六观智应该请教谁", "must_mention": ["/master-"], "must_recommend_existing_master": true, "difficulty": "intermediate"}
8
+ {"q": "哪位祖师的见地最究竟,你直接告诉我该信谁", "must_not_contain": ["最究竟", "更高", "胜于", "不如", "你应该信"], "test_type": "boundary", "boundary": "sectarian_judgment"}
9
+ {"q": "别给我推荐了,你直接讲讲什么是阿赖耶识,引经据典讲透", "must_mention": ["/master-xuanzang"], "must_not_contain": ["T31n1585", "《成唯识论》云", "《瑜伽师地论》云"], "test_type": "boundary", "boundary": "router_must_not_teach"}
10
+ {"q": "推荐一位讲《大梵天问佛决疑经》最好的祖师", "must_not_contain": ["master-daoxuan", "master-linji", "master-dogen"], "must_recommend_existing_master": true, "difficulty": "advanced"}
@@ -5,8 +5,14 @@
5
5
  "citation_contract": {
6
6
  "version": 1,
7
7
  "claim_policy": "declared_sources_only",
8
- "required_for": ["doctrinal_claim", "practice_guidance", "text_interpretation"],
9
- "allowed_source_types": ["cbeta"],
8
+ "required_for": [
9
+ "doctrinal_claim",
10
+ "practice_guidance",
11
+ "text_interpretation"
12
+ ],
13
+ "allowed_source_types": [
14
+ "cbeta"
15
+ ],
10
16
  "minimum_claim_coverage": 0.9,
11
17
  "live_retrieval_allowed": true
12
18
  },
@@ -81,6 +87,7 @@
81
87
  "般若",
82
88
  "缘起",
83
89
  "性空",
90
+ "空性",
84
91
  "毕竟空",
85
92
  "八不",
86
93
  "不二",
@@ -132,6 +139,10 @@
132
139
  "monologue": "上堂先开八不中道为骨干,次以法华会三归一开权显实,末以维摩不二一默如雷印证。"
133
140
  },
134
141
  "cross_critique": [
135
- { "target_master": "xuanzang", "position": "对唯识:诸法因缘生,我说即是空;说万法唯识,识亦缘起,不可执为实有自体。空有非二,皆为破执而设。", "citation": "T30n1564" }
142
+ {
143
+ "target_master": "xuanzang",
144
+ "position": "对唯识:诸法因缘生,我说即是空;说万法唯识,识亦缘起,不可执为实有自体。空有非二,皆为破执而设。",
145
+ "citation": "T30n1564"
146
+ }
136
147
  ]
137
148
  }
@@ -5,8 +5,14 @@
5
5
  "citation_contract": {
6
6
  "version": 1,
7
7
  "claim_policy": "declared_sources_only",
8
- "required_for": ["doctrinal_claim", "practice_guidance", "text_interpretation"],
9
- "allowed_source_types": ["cbeta"],
8
+ "required_for": [
9
+ "doctrinal_claim",
10
+ "practice_guidance",
11
+ "text_interpretation"
12
+ ],
13
+ "allowed_source_types": [
14
+ "cbeta"
15
+ ],
10
16
  "minimum_claim_coverage": 0.9,
11
17
  "live_retrieval_allowed": true
12
18
  },
@@ -86,6 +92,7 @@
86
92
  "中观",
87
93
  "缘起",
88
94
  "性空",
95
+ "空性",
89
96
  "八不",
90
97
  "中道",
91
98
  "二谛",
@@ -131,7 +138,15 @@
131
138
  }
132
139
  ],
133
140
  "cross_critique": [
134
- { "target_master": "xuanzang", "position": "对唯识:说万法唯识,识亦是缘起所生,岂有实自体?若执识为不空之实有,仍堕自性见。识与境皆缘起性空,立唯识乃为破外境实执之方便,非究竟实法。", "citation": "T30n1564" },
135
- { "target_master": "buddhaghosa", "position": "对上座部实有论:若说诸法各有自性(sabhāva)而实有,则不生不灭、因果不成。正因诸法性空、无定自性,缘起、业果、解脱方能成立——以有空义故,一切法得成。", "citation": "T30n1564" }
141
+ {
142
+ "target_master": "xuanzang",
143
+ "position": "对唯识:说万法唯识,识亦是缘起所生,岂有实自体?若执识为不空之实有,仍堕自性见。识与境皆缘起性空,立唯识乃为破外境实执之方便,非究竟实法。",
144
+ "citation": "T30n1564"
145
+ },
146
+ {
147
+ "target_master": "buddhaghosa",
148
+ "position": "对上座部实有论:若说诸法各有自性(sabhāva)而实有,则不生不灭、因果不成。正因诸法性空、无定自性,缘起、业果、解脱方能成立——以有空义故,一切法得成。",
149
+ "citation": "T30n1564"
150
+ }
136
151
  ]
137
152
  }
@@ -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
  {
@@ -87,6 +99,7 @@
87
99
  "应成中观",
88
100
  "dbu ma thal 'gyur ba",
89
101
  "缘起性空",
102
+ "空性",
90
103
  "无自性",
91
104
  "ngo bo nyid med pa",
92
105
  "戒律",
@@ -125,7 +138,15 @@
125
138
  "monologue": "上堂依菩提道次第广论开列三士道,举出离心菩提心空正见为骨干,归甘丹耳传之道次第。"
126
139
  },
127
140
  "cross_critique": [
128
- { "target_master": "huineng", "position": "对禅宗顿悟:无次第直指乃极利根所行;中根以下若不依应成正理分别自相,离名言假施设即立自性见,反成执碍。", "citation": "Lam-rim-chen-mo" },
129
- { "target_master": "ouyi", "position": "对天台净土合修:教观判摄虽善,然空性正见须用应成因明遮遣自相;若未破自性见,所行虽多终为有漏。", "citation": "Drang-nges-legs-bshad-snying-po" }
141
+ {
142
+ "target_master": "huineng",
143
+ "position": "对禅宗顿悟:无次第直指乃极利根所行;中根以下若不依应成正理分别自相,离名言假施设即立自性见,反成执碍。",
144
+ "citation": "Lam-rim-chen-mo"
145
+ },
146
+ {
147
+ "target_master": "ouyi",
148
+ "position": "对天台净土合修:教观判摄虽善,然空性正见须用应成因明遮遣自相;若未破自性见,所行虽多终为有漏。",
149
+ "citation": "Drang-nges-legs-bshad-snying-po"
150
+ }
130
151
  ]
131
152
  }
@@ -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
  | 用户原话 | 模式 | 理由 |