diff --git a/prebuilt/ajahn_chah/meta.json b/prebuilt/ajahn_chah/meta.json index cb40784..c73e386 100644 --- a/prebuilt/ajahn_chah/meta.json +++ b/prebuilt/ajahn_chah/meta.json @@ -5,17 +5,69 @@ "tradition": "南传", "school": "泰国森林传承", "era": "1918-1992", - "languages": ["en", "th", "pi", "zh"], + "languages": [ + "en", + "th", + "pi", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "suttacentral", "id": "dn22", "title": "Mahāsatipaṭṭhāna Sutta"}, - {"type": "suttacentral", "id": "sn56.11", "title": "Dhammacakkappavattana Sutta"}, - {"type": "suttacentral", "id": "sn35.28", "title": "Ādittapariyāya Sutta"}, - {"type": "suttacentral", "id": "mn10", "title": "Satipaṭṭhāna Sutta"}, - {"type": "suttacentral", "id": "sn22.59", "title": "Anattalakkhaṇa Sutta"} + { + "type": "suttacentral", + "id": "dn22", + "title": "Mahāsatipaṭṭhāna Sutta" + }, + { + "type": "suttacentral", + "id": "sn56.11", + "title": "Dhammacakkappavattana Sutta" + }, + { + "type": "suttacentral", + "id": "sn35.28", + "title": "Ādittapariyāya Sutta" + }, + { + "type": "suttacentral", + "id": "mn10", + "title": "Satipaṭṭhāna Sutta" + }, + { + "type": "suttacentral", + "id": "sn22.59", + "title": "Anattalakkhaṇa Sutta" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_suttacentral_ids": [ + "dn22", + "sn56.11", + "sn35.28", + "mn10", + "sn22.59" + ], + "traditions": [ + "南传", + "上座部" + ], + "dictionary_sources": [ + "dpd", + "pts", + "ncped" + ], + "keywords": [ + "anicca", + "dukkha", + "anatta", + "satipatthana", + "vipassana", + "无常", + "正念" + ] + } } diff --git a/prebuilt/fazang/meta.json b/prebuilt/fazang/meta.json index a0d5578..a1666e2 100644 --- a/prebuilt/fazang/meta.json +++ b/prebuilt/fazang/meta.json @@ -4,17 +4,64 @@ "tradition": "汉传", "school": "华严宗", "era": "643-712", - "languages": ["zh-classical", "zh"], + "languages": [ + "zh-classical", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T10n0279", "title": "大方广佛华严经(八十华严)"}, - {"type": "cbeta", "id": "T35n1733", "title": "华严经探玄记"}, - {"type": "cbeta", "id": "T45n1866", "title": "华严一乘教义分齐章"}, - {"type": "cbeta", "id": "T45n1875", "title": "华严经义海百门"}, - {"type": "cbeta", "id": "T45n1876", "title": "修华严奥旨妄尽还源观"} + { + "type": "cbeta", + "id": "T10n0279", + "title": "大方广佛华严经(八十华严)" + }, + { + "type": "cbeta", + "id": "T35n1733", + "title": "华严经探玄记" + }, + { + "type": "cbeta", + "id": "T45n1866", + "title": "华严一乘教义分齐章" + }, + { + "type": "cbeta", + "id": "T45n1875", + "title": "华严经义海百门" + }, + { + "type": "cbeta", + "id": "T45n1876", + "title": "修华严奥旨妄尽还源观" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T10n0279", + "T35n1733", + "T45n1866", + "T45n1875", + "T45n1876" + ], + "traditions": [ + "华严" + ], + "dictionary_sources": [ + "foguang", + "dingfubao" + ], + "keywords": [ + "法界", + "华严", + "十玄", + "六相", + "事事无碍", + "因陀罗网" + ] + } } diff --git a/prebuilt/huineng/meta.json b/prebuilt/huineng/meta.json index cc193de..e385743 100644 --- a/prebuilt/huineng/meta.json +++ b/prebuilt/huineng/meta.json @@ -4,15 +4,54 @@ "tradition": "汉传", "school": "禅宗", "era": "638-713", - "languages": ["zh-classical", "zh"], + "languages": [ + "zh-classical", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T48n2008", "title": "六祖大师法宝坛经"}, - {"type": "cbeta", "id": "T08n0235", "title": "金刚般若波罗蜜经"}, - {"type": "cbeta", "id": "T14n0475", "title": "维摩诘所说经"} + { + "type": "cbeta", + "id": "T48n2008", + "title": "六祖大师法宝坛经" + }, + { + "type": "cbeta", + "id": "T08n0235", + "title": "金刚般若波罗蜜经" + }, + { + "type": "cbeta", + "id": "T14n0475", + "title": "维摩诘所说经" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T48n2008", + "T08n0235", + "T14n0475" + ], + "traditions": [ + "禅宗", + "般若" + ], + "dictionary_sources": [ + "foguang", + "dingfubao" + ], + "keywords": [ + "自性", + "顿悟", + "见性", + "无念", + "禅", + "本心", + "般若" + ] + } } diff --git a/prebuilt/kumarajiva/meta.json b/prebuilt/kumarajiva/meta.json index ba264f3..202b7ef 100644 --- a/prebuilt/kumarajiva/meta.json +++ b/prebuilt/kumarajiva/meta.json @@ -5,18 +5,73 @@ "tradition": "汉传", "school": "三论宗/中观", "era": "344-413", - "languages": ["zh-classical", "zh", "sa"], + "languages": [ + "zh-classical", + "zh", + "sa" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T09n0262", "title": "妙法莲华经"}, - {"type": "cbeta", "id": "T08n0235", "title": "金刚般若波罗蜜经"}, - {"type": "cbeta", "id": "T14n0475", "title": "维摩诘所说经"}, - {"type": "cbeta", "id": "T30n1564", "title": "中论"}, - {"type": "cbeta", "id": "T25n1509", "title": "大智度论"}, - {"type": "cbeta", "id": "T12n0366", "title": "佛说阿弥陀经"} + { + "type": "cbeta", + "id": "T09n0262", + "title": "妙法莲华经" + }, + { + "type": "cbeta", + "id": "T08n0235", + "title": "金刚般若波罗蜜经" + }, + { + "type": "cbeta", + "id": "T14n0475", + "title": "维摩诘所说经" + }, + { + "type": "cbeta", + "id": "T30n1564", + "title": "中论" + }, + { + "type": "cbeta", + "id": "T25n1509", + "title": "大智度论" + }, + { + "type": "cbeta", + "id": "T12n0366", + "title": "佛说阿弥陀经" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T09n0262", + "T08n0235", + "T14n0475", + "T30n1564", + "T25n1509" + ], + "traditions": [ + "中观", + "三论", + "般若" + ], + "dictionary_sources": [ + "foguang", + "dingfubao", + "soothill" + ], + "keywords": [ + "空", + "中道", + "般若", + "法华", + "不二", + "八不" + ] + } } diff --git a/prebuilt/ouyi/meta.json b/prebuilt/ouyi/meta.json index 28474fb..6a804a5 100644 --- a/prebuilt/ouyi/meta.json +++ b/prebuilt/ouyi/meta.json @@ -4,17 +4,68 @@ "tradition": "汉传", "school": "天台/净土(跨宗派)", "era": "1599-1655", - "languages": ["zh-classical", "zh"], + "languages": [ + "zh-classical", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T37n1762", "title": "阿弥陀经要解"}, - {"type": "cbeta", "id": "T09n0262", "title": "妙法莲华经"}, - {"type": "cbeta", "id": "T24n1484", "title": "梵网经"}, - {"type": "cbeta", "id": "T12n0366", "title": "佛说阿弥陀经"}, - {"type": "cbeta", "id": "T31n1585", "title": "成唯识论"} + { + "type": "cbeta", + "id": "T37n1762", + "title": "阿弥陀经要解" + }, + { + "type": "cbeta", + "id": "T09n0262", + "title": "妙法莲华经" + }, + { + "type": "cbeta", + "id": "T24n1484", + "title": "梵网经" + }, + { + "type": "cbeta", + "id": "T12n0366", + "title": "佛说阿弥陀经" + }, + { + "type": "cbeta", + "id": "T31n1585", + "title": "成唯识论" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T37n1762", + "T09n0262", + "T24n1484", + "T12n0366", + "T31n1585" + ], + "traditions": [ + "天台", + "净土", + "唯识", + "律宗" + ], + "dictionary_sources": [ + "foguang", + "dingfubao", + "tiantai" + ], + "keywords": [ + "天台", + "净土", + "念佛", + "六信", + "止观", + "性相融会" + ] + } } diff --git a/prebuilt/tsongkhapa/meta.json b/prebuilt/tsongkhapa/meta.json index f66b62c..5b1b9b8 100644 --- a/prebuilt/tsongkhapa/meta.json +++ b/prebuilt/tsongkhapa/meta.json @@ -5,17 +5,54 @@ "tradition": "藏传", "school": "格鲁派", "era": "1357-1419", - "languages": ["bo", "zh", "sa"], + "languages": [ + "bo", + "zh", + "sa" + ], "fojin_entity_id": null, "sources": [ - {"type": "text", "title": "菩提道次第广论 (Lam Rim Chen Mo)"}, - {"type": "text", "title": "密宗道次第广论 (sNgags Rim Chen Mo)"}, - {"type": "text", "title": "入中论善显密意疏"}, - {"type": "text", "title": "辨了不了义善说藏论"}, - {"type": "text", "title": "三主要道 (Lam gTso rNam gSum)"} + { + "type": "text", + "title": "菩提道次第广论 (Lam Rim Chen Mo)" + }, + { + "type": "text", + "title": "密宗道次第广论 (sNgags Rim Chen Mo)" + }, + { + "type": "text", + "title": "入中论善显密意疏" + }, + { + "type": "text", + "title": "辨了不了义善说藏论" + }, + { + "type": "text", + "title": "三主要道 (Lam gTso rNam gSum)" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "traditions": [ + "藏传", + "格鲁" + ], + "dictionary_sources": [ + "rangjung", + "hopkins" + ], + "keywords": [ + "道次第", + "三主要道", + "菩提心", + "空性", + "止观", + "出离心" + ] + } } diff --git a/prebuilt/xuanzang/meta.json b/prebuilt/xuanzang/meta.json index acb6063..8444078 100644 --- a/prebuilt/xuanzang/meta.json +++ b/prebuilt/xuanzang/meta.json @@ -4,18 +4,71 @@ "tradition": "汉传", "school": "法相唯识宗", "era": "602-664", - "languages": ["zh-classical", "zh", "sa"], + "languages": [ + "zh-classical", + "zh", + "sa" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T07n0220", "title": "大般若波罗蜜多经"}, - {"type": "cbeta", "id": "T30n1579", "title": "瑜伽师地论"}, - {"type": "cbeta", "id": "T31n1585", "title": "成唯识论"}, - {"type": "cbeta", "id": "T08n0251", "title": "般若波罗蜜多心经"}, - {"type": "cbeta", "id": "T29n1558", "title": "阿毗达磨俱舍论"}, - {"type": "cbeta", "id": "T51n2087", "title": "大唐西域记"} + { + "type": "cbeta", + "id": "T07n0220", + "title": "大般若波罗蜜多经" + }, + { + "type": "cbeta", + "id": "T30n1579", + "title": "瑜伽师地论" + }, + { + "type": "cbeta", + "id": "T31n1585", + "title": "成唯识论" + }, + { + "type": "cbeta", + "id": "T08n0251", + "title": "般若波罗蜜多心经" + }, + { + "type": "cbeta", + "id": "T29n1558", + "title": "阿毗达磨俱舍论" + }, + { + "type": "cbeta", + "id": "T51n2087", + "title": "大唐西域记" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T07n0220", + "T30n1579", + "T31n1585", + "T08n0251" + ], + "traditions": [ + "唯识", + "法相" + ], + "dictionary_sources": [ + "foguang", + "dingfubao", + "yogacara" + ], + "keywords": [ + "唯识", + "阿赖耶识", + "三性", + "五位百法", + "因明", + "瑜伽" + ] + } } diff --git a/prebuilt/xuyun/meta.json b/prebuilt/xuyun/meta.json index 7b1ff0b..343920c 100644 --- a/prebuilt/xuyun/meta.json +++ b/prebuilt/xuyun/meta.json @@ -4,16 +4,57 @@ "tradition": "汉传", "school": "禅宗(五宗兼嗣)", "era": "1840-1959", - "languages": ["zh"], + "languages": [ + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T19n0945", "title": "大佛顶首楞严经"}, - {"type": "cbeta", "id": "T08n0235", "title": "金刚般若波罗蜜经"}, - {"type": "cbeta", "id": "T48n2008", "title": "六祖大师法宝坛经"}, - {"type": "cbeta", "id": "T17n0842", "title": "大方广圆觉修多罗了义经"} + { + "type": "cbeta", + "id": "T19n0945", + "title": "大佛顶首楞严经" + }, + { + "type": "cbeta", + "id": "T08n0235", + "title": "金刚般若波罗蜜经" + }, + { + "type": "cbeta", + "id": "T48n2008", + "title": "六祖大师法宝坛经" + }, + { + "type": "cbeta", + "id": "T17n0842", + "title": "大方广圆觉修多罗了义经" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T19n0945", + "T08n0235", + "T48n2008", + "T17n0842" + ], + "traditions": [ + "禅宗" + ], + "dictionary_sources": [ + "foguang", + "dingfubao" + ], + "keywords": [ + "参禅", + "话头", + "疑情", + "开悟", + "禅七", + "禅净双修" + ] + } } diff --git a/prebuilt/yinguang/meta.json b/prebuilt/yinguang/meta.json index 18f48c6..16f2731 100644 --- a/prebuilt/yinguang/meta.json +++ b/prebuilt/yinguang/meta.json @@ -4,18 +4,70 @@ "tradition": "汉传", "school": "净土宗", "era": "1861-1940", - "languages": ["zh-classical", "zh"], + "languages": [ + "zh-classical", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "X62n1182", "title": "印光法师文钞正编"}, - {"type": "cbeta", "id": "X62n1183", "title": "印光法师文钞续编"}, - {"type": "cbeta", "id": "X62n1184", "title": "印光法师文钞三编"}, - {"type": "cbeta", "id": "T12n0366", "title": "佛说阿弥陀经"}, - {"type": "cbeta", "id": "T12n0365", "title": "佛说观无量寿佛经"}, - {"type": "cbeta", "id": "T12n0360", "title": "佛说无量寿经"} + { + "type": "cbeta", + "id": "X62n1182", + "title": "印光法师文钞正编" + }, + { + "type": "cbeta", + "id": "X62n1183", + "title": "印光法师文钞续编" + }, + { + "type": "cbeta", + "id": "X62n1184", + "title": "印光法师文钞三编" + }, + { + "type": "cbeta", + "id": "T12n0366", + "title": "佛说阿弥陀经" + }, + { + "type": "cbeta", + "id": "T12n0365", + "title": "佛说观无量寿佛经" + }, + { + "type": "cbeta", + "id": "T12n0360", + "title": "佛说无量寿经" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "X62n1182", + "X62n1183", + "X62n1184", + "T12n0366", + "T12n0365", + "T12n0360" + ], + "traditions": [ + "净土" + ], + "dictionary_sources": [ + "foguang", + "dingfubao" + ], + "keywords": [ + "念佛", + "净土", + "往生", + "信愿行", + "阿弥陀", + "极乐" + ] + } } diff --git a/prebuilt/zhiyi/meta.json b/prebuilt/zhiyi/meta.json index b33b2c8..3946f16 100644 --- a/prebuilt/zhiyi/meta.json +++ b/prebuilt/zhiyi/meta.json @@ -4,17 +4,66 @@ "tradition": "汉传", "school": "天台宗", "era": "538-597", - "languages": ["zh-classical", "zh"], + "languages": [ + "zh-classical", + "zh" + ], "fojin_entity_id": null, "sources": [ - {"type": "cbeta", "id": "T46n1911", "title": "摩诃止观"}, - {"type": "cbeta", "id": "T33n1718", "title": "妙法莲华经玄义"}, - {"type": "cbeta", "id": "T34n1718", "title": "妙法莲华经文句"}, - {"type": "cbeta", "id": "T46n1915", "title": "修习止观坐禅法要"}, - {"type": "cbeta", "id": "T09n0262", "title": "妙法莲华经"} + { + "type": "cbeta", + "id": "T46n1911", + "title": "摩诃止观" + }, + { + "type": "cbeta", + "id": "T33n1718", + "title": "妙法莲华经玄义" + }, + { + "type": "cbeta", + "id": "T34n1718", + "title": "妙法莲华经文句" + }, + { + "type": "cbeta", + "id": "T46n1915", + "title": "修习止观坐禅法要" + }, + { + "type": "cbeta", + "id": "T09n0262", + "title": "妙法莲华经" + } ], "version": "1.0.0", "created_at": "2026-04-04", "updated_at": "2026-04-04", - "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。" + "disclaimer": "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。", + "search_scope": { + "primary_cbeta_ids": [ + "T46n1911", + "T33n1718", + "T34n1718", + "T46n1915", + "T09n0262" + ], + "traditions": [ + "天台", + "法华" + ], + "dictionary_sources": [ + "foguang", + "dingfubao", + "tiantai" + ], + "keywords": [ + "一念三千", + "三谛", + "止观", + "五时八教", + "法华", + "圆融" + ] + } } diff --git a/tools/cross_reference.py b/tools/cross_reference.py new file mode 100644 index 0000000..9ee6e08 --- /dev/null +++ b/tools/cross_reference.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +""" +Cross-Reference — find connections between teachers using FoJin KG. + +Usage: + python3 tools/cross_reference.py lineage "印光" "蕅益" + python3 tools/cross_reference.py concept "空性" --teachers xuanzang,kumarajiva +""" + +import argparse +import json +import os +import sys +from pathlib import Path +from typing import Optional + +# Allow running from project root or tools/ +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT / "tools")) + +from fojin_bridge import FojinBridge, create_bridge + + +# ── Teacher registry ──────────────────────────────────────────── + +PREBUILT_DIR = PROJECT_ROOT / "prebuilt" + + +def load_teacher_meta(slug: str) -> dict: + """Load a teacher's meta.json by slug.""" + meta_path = PREBUILT_DIR / slug / "meta.json" + if not meta_path.exists(): + raise FileNotFoundError(f"Teacher not found: {slug}") + with open(meta_path, "r", encoding="utf-8") as f: + return json.load(f) + + +def list_teachers() -> list[str]: + """Return all available teacher slugs.""" + return sorted( + d.name for d in PREBUILT_DIR.iterdir() + if d.is_dir() and (d / "meta.json").exists() + ) + + +def find_teacher_by_name(name: str) -> Optional[dict]: + """Find a teacher by Chinese name, English name, or slug.""" + for slug in list_teachers(): + meta = load_teacher_meta(slug) + if name in ( + meta.get("name"), + meta.get("name_en"), + meta.get("name_sa"), + meta.get("name_bo"), + meta.get("slug"), + slug, + ): + return meta + return None + + +# ── Lineage subcommand ────────────────────────────────────────── + +def cmd_lineage(bridge: FojinBridge, person_a: str, person_b: str) -> str: + """ + Search the KG for relationship paths between two people. + + Returns formatted text describing the lineage connection. + """ + lines: list[str] = [] + lines.append(f"## 师承关系查询: {person_a} <-> {person_b}") + lines.append("") + + # Search for both entities in the KG + result_a = bridge.search_kg_entities(person_a, entity_type="person", limit=5) + result_b = bridge.search_kg_entities(person_b, entity_type="person", limit=5) + + entities_a = result_a.get("data", result_a.get("results", [])) + entities_b = result_b.get("data", result_b.get("results", [])) + + if not entities_a: + lines.append(f"- 未在知识图谱中找到: {person_a}") + if not entities_b: + lines.append(f"- 未在知识图谱中找到: {person_b}") + + if not entities_a or not entities_b: + lines.append("") + lines.append("### 建议") + lines.append("- 尝试使用不同的名称(法号、别名等)") + lines.append("- 查看 prebuilt/ 目录下已有的法师配置") + return "\n".join(lines) + + entity_a = entities_a[0] + entity_b = entities_b[0] + id_a = entity_a.get("id") + id_b = entity_b.get("id") + + lines.append(f"### {person_a}") + lines.append(f"- KG实体: {entity_a.get('name', person_a)} (ID: {id_a})") + lines.append("") + + lines.append(f"### {person_b}") + lines.append(f"- KG实体: {entity_b.get('name', person_b)} (ID: {id_b})") + lines.append("") + + # Fetch relationship graphs for both, looking for teacher-student predicates + lines.append("### 关系图谱") + lines.append("") + + graph_a = bridge.get_kg_graph( + id_a, depth=3, max_nodes=100, + predicates="teacher_of,student_of,disciple_of,master_of" + ) + + nodes = {n["id"]: n for n in graph_a.get("nodes", [])} + edges = graph_a.get("edges", graph_a.get("links", [])) + + # Check if person_b appears in person_a's graph + found_connection = False + for node in nodes.values(): + node_name = node.get("name", "") + if person_b in node_name or node.get("id") == id_b: + found_connection = True + break + + if found_connection: + lines.append(f"在 {person_a} 的关系图谱中找到 {person_b}:") + lines.append("") + for edge in edges: + src = nodes.get(edge.get("source"), {}).get("name", str(edge.get("source"))) + tgt = nodes.get(edge.get("target"), {}).get("name", str(edge.get("target"))) + pred = edge.get("predicate", edge.get("label", "related_to")) + lines.append(f" {src} --[{pred}]--> {tgt}") + else: + lines.append(f"在3层关系内未找到 {person_a} 与 {person_b} 的直接师承路径。") + lines.append("") + + # Show each person's immediate relations for context + lines.append(f"#### {person_a} 的师承关系:") + for edge in edges[:10]: + src = nodes.get(edge.get("source"), {}).get("name", str(edge.get("source"))) + tgt = nodes.get(edge.get("target"), {}).get("name", str(edge.get("target"))) + pred = edge.get("predicate", edge.get("label", "related_to")) + lines.append(f" {src} --[{pred}]--> {tgt}") + + graph_b = bridge.get_kg_graph( + id_b, depth=2, max_nodes=50, + predicates="teacher_of,student_of,disciple_of,master_of" + ) + nodes_b = {n["id"]: n for n in graph_b.get("nodes", [])} + edges_b = graph_b.get("edges", graph_b.get("links", [])) + + lines.append("") + lines.append(f"#### {person_b} 的师承关系:") + for edge in edges_b[:10]: + src = nodes_b.get(edge.get("source"), {}).get("name", str(edge.get("source"))) + tgt = nodes_b.get(edge.get("target"), {}).get("name", str(edge.get("target"))) + pred = edge.get("predicate", edge.get("label", "related_to")) + lines.append(f" {src} --[{pred}]--> {tgt}") + + # Check if both are prebuilt teachers and note shared traditions + lines.append("") + lines.append("### 传承交集分析") + meta_a = find_teacher_by_name(person_a) + meta_b = find_teacher_by_name(person_b) + if meta_a and meta_b: + scope_a = meta_a.get("search_scope", {}) + scope_b = meta_b.get("search_scope", {}) + shared_traditions = set(scope_a.get("traditions", [])) & set(scope_b.get("traditions", [])) + shared_keywords = set(scope_a.get("keywords", [])) & set(scope_b.get("keywords", [])) + + if shared_traditions: + lines.append(f"- 共同传承: {', '.join(shared_traditions)}") + if shared_keywords: + lines.append(f"- 共同关键词: {', '.join(shared_keywords)}") + if not shared_traditions and not shared_keywords: + lines.append("- 两位法师属于不同传承,无直接交集") + else: + lines.append("- 部分法师不在预置列表中,无法进行本地交集分析") + + return "\n".join(lines) + + +# ── Concept subcommand ────────────────────────────────────────── + +def cmd_concept(bridge: FojinBridge, concept: str, teacher_slugs: list[str]) -> str: + """ + Search the same concept across multiple teachers' primary sources. + + Returns formatted text comparing how different teachers discuss the concept. + """ + lines: list[str] = [] + lines.append(f"## 跨传承概念对比: {concept}") + lines.append("") + + # Dictionary lookup first + lines.append("### 辞典释义") + try: + dict_result = bridge.search_dictionary_grouped(concept) + entries = dict_result.get("data", dict_result.get("results", [])) + if entries: + for group in entries[:3]: + source = group.get("source", "unknown") + items = group.get("entries", group.get("items", [])) + if items: + entry = items[0] + term = entry.get("term", entry.get("headword", concept)) + definition = entry.get("definition", entry.get("content", "")) + # Truncate long definitions + if len(definition) > 200: + definition = definition[:200] + "..." + lines.append(f"- **{source}** [{term}]: {definition}") + else: + lines.append(f"- 未找到「{concept}」的辞典条目") + except Exception as e: + lines.append(f"- 辞典查询失败: {e}") + + lines.append("") + + # For each teacher, search their primary sources + for slug in teacher_slugs: + try: + meta = load_teacher_meta(slug) + except FileNotFoundError: + lines.append(f"### {slug} (未找到)") + lines.append(f"- 法师 '{slug}' 不在预置列表中") + lines.append("") + continue + + name = meta.get("name", slug) + scope = meta.get("search_scope", {}) + traditions = scope.get("traditions", []) + + lines.append(f"### {name} ({', '.join(traditions)})") + + # Build source filter from primary CBETA/SuttaCentral IDs + primary_ids = scope.get("primary_cbeta_ids", []) + scope.get("primary_suttacentral_ids", []) + source_filter = ",".join(primary_ids) if primary_ids else None + + # Search in teacher's primary texts + try: + results = bridge.search_content(concept, sources=source_filter, size=5) + hits = results.get("data", results.get("results", [])) + if hits: + for hit in hits[:3]: + title = hit.get("title", hit.get("text_title", "")) + highlight = hit.get("highlight", hit.get("snippet", "")) + if isinstance(highlight, dict): + highlight = highlight.get("content", [""])[0] + if isinstance(highlight, list): + highlight = highlight[0] if highlight else "" + # Truncate + if len(highlight) > 150: + highlight = highlight[:150] + "..." + lines.append(f"- 《{title}》: {highlight}") + else: + lines.append(f"- 在主要典籍中未搜到「{concept}」的直接引用") + except Exception as e: + lines.append(f"- 搜索失败: {e}") + + # Also search dictionary with teacher-specific sources + dict_sources = scope.get("dictionary_sources", []) + if dict_sources: + try: + for ds in dict_sources[:2]: + dict_res = bridge.search_dictionary(concept, source=ds, size=1) + entries = dict_res.get("data", dict_res.get("results", [])) + if entries: + entry = entries[0] + definition = entry.get("definition", entry.get("content", "")) + if len(definition) > 100: + definition = definition[:100] + "..." + lines.append(f"- [{ds}辞典]: {definition}") + except Exception: + pass # Dictionary lookup is best-effort + + lines.append("") + + # Cross-tradition comparison summary + lines.append("### 传承视角对比") + tradition_map: dict[str, list[str]] = {} + for slug in teacher_slugs: + try: + meta = load_teacher_meta(slug) + scope = meta.get("search_scope", {}) + for t in scope.get("traditions", []): + tradition_map.setdefault(t, []).append(meta.get("name", slug)) + except FileNotFoundError: + pass + + for tradition, teachers in tradition_map.items(): + lines.append(f"- **{tradition}**: {', '.join(teachers)}") + + if len(tradition_map) > 1: + lines.append("") + lines.append( + f"共涉及 {len(tradition_map)} 个传承," + f"建议对比各传承对「{concept}」的不同诠释角度。" + ) + + return "\n".join(lines) + + +# ── CLI ───────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser( + description="Cross-reference Buddhist teachers and concepts via FoJin KG", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""Examples: + %(prog)s lineage "印光" "蕅益" + %(prog)s concept "空性" --teachers xuanzang,kumarajiva + %(prog)s concept "止观" --teachers zhiyi,ouyi,xuyun + %(prog)s list +""", + ) + sub = parser.add_subparsers(dest="command") + + # lineage + p_lineage = sub.add_parser("lineage", help="查询两人师承关系") + p_lineage.add_argument("person_a", help="第一位人物名称") + p_lineage.add_argument("person_b", help="第二位人物名称") + + # concept + p_concept = sub.add_parser("concept", help="跨传承概念对比") + p_concept.add_argument("concept", help="要比较的概念") + p_concept.add_argument( + "--teachers", "-t", required=True, + help="法师slug列表,逗号分隔 (如: xuanzang,kumarajiva)" + ) + + # list + sub.add_parser("list", help="列出所有可用法师") + + args = parser.parse_args() + + if args.command == "list": + print("Available teachers:") + for slug in list_teachers(): + meta = load_teacher_meta(slug) + scope = meta.get("search_scope", {}) + traditions = ", ".join(scope.get("traditions", [])) + print(f" {slug:16s} {meta['name']} ({traditions})") + return + + if args.command is None: + parser.print_help() + return + + bridge = create_bridge() + + if args.command == "lineage": + output = cmd_lineage(bridge, args.person_a, args.person_b) + elif args.command == "concept": + teacher_slugs = [s.strip() for s in args.teachers.split(",")] + output = cmd_concept(bridge, args.concept, teacher_slugs) + else: + parser.print_help() + return + + print(output) + + +if __name__ == "__main__": + main()