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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/GEMINI.md +1 -1
- package/README.md +52 -297
- package/README_EN.md +52 -278
- package/bin/cli.mjs +237 -2
- package/gemini-extension.json +1 -1
- package/hooks/session-start +4 -1
- package/package.json +3 -2
- package/prebuilt/master-curriculum/SKILL.md +1 -1
- package/prebuilt/master-debate/SKILL.md +1 -1
- package/prebuilt/master-help/SKILL.md +86 -0
- package/prebuilt/master-help/tests/fidelity.jsonl +10 -0
- package/prebuilt/master-kumarajiva/meta.json +14 -3
- package/prebuilt/master-nagarjuna/meta.json +19 -4
- package/prebuilt/master-tsongkhapa/meta.json +26 -5
- package/references/teaching-modes.md +8 -1
- package/routing.json +209 -0
- package/scripts/check-gate-liveness.py +222 -0
- package/scripts/test-fidelity.py +320 -49
- package/scripts/tests/test_check_gate_liveness.py +232 -0
- package/scripts/tests/test_check_response.py +190 -0
- package/scripts/tests/test_fidelity_providers.py +202 -0
- package/scripts/tests/test_select_fidelity_smoke.py +2 -2
- package/scripts/tests/test_validate.py +145 -0
- package/scripts/tests/test_validate_citation_contract.py +1 -1
- package/scripts/tests/test_validate_fidelity.py +2 -2
- package/scripts/tests/test_validate_workflow.py +21 -2
- package/scripts/validate-fidelity.py +6 -1
- package/scripts/validate-routing.py +254 -0
- package/scripts/validate.py +63 -36
- package/skill-catalog.json +83 -20
- /package/prebuilt/{compare → compare-masters}/SKILL.md +0 -0
- /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
|
|
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")) {
|
package/gemini-extension.json
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/hooks/session-start
CHANGED
|
@@ -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} /
|
|
67
|
+
${MASTERS_LIST} /master-help — not 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.
|
|
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": [
|
|
9
|
-
|
|
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
|
-
{
|
|
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": [
|
|
9
|
-
|
|
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
|
-
{
|
|
135
|
-
|
|
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": [
|
|
12
|
-
|
|
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": [
|
|
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
|
-
{
|
|
129
|
-
|
|
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
|
-
- 学修次第 / 学习计划 / 入门 / 先学什么 / 从哪开始 / 路径推荐 /
|
|
58
|
+
- 学修次第 / 学习计划 / 入门 / 先学什么 / 从哪开始 / 开始学 / 应该读 / 路径推荐 / 按什么顺序 / curriculum / roadmap
|
|
54
59
|
- 问"禅宗从哪开始学" → 时序路径,不是比较
|
|
55
60
|
|
|
61
|
+
> ⚠️ `怎么修` **不是**本模式的触发词。它是求法,不是求路径——"这个怎么修"归单 master skill(见本文决策树末端),"从哪开始学"才归这里。本文此处曾同时把 `怎么修` 列为 curriculum 触发词又在决策树里判给单 master,两处矛盾已按后者裁定。
|
|
62
|
+
|
|
56
63
|
## 示例输入分类
|
|
57
64
|
|
|
58
65
|
| 用户原话 | 模式 | 理由 |
|