master-skill 0.12.8 → 0.12.10
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-extension.json +1 -1
- package/package.json +1 -1
- package/prebuilt/master-buddhaghosa/references/teaching.md +1 -1
- package/prebuilt/master-fazang/references/voice.md +1 -1
- package/prebuilt/master-milarepa/SKILL.md +2 -0
- package/prebuilt/master-milarepa/meta.json +7 -1
- package/prebuilt/master-milarepa/references/teaching.md +13 -7
- package/prebuilt/master-milarepa/references/voice.md +4 -4
- package/prebuilt/master-milarepa/sources/INDEX.md +1 -1
- package/prebuilt/master-nagarjuna/references/teaching.md +1 -1
- package/prebuilt/master-nagarjuna/references/voice.md +3 -3
- package/prebuilt/master-ouyi/references/voice.md +2 -2
- package/prebuilt/master-xuanzang/references/voice.md +5 -3
- package/prebuilt/master-yinguang/references/voice.md +3 -3
- package/prebuilt/master-zhiyi/references/voice.md +5 -3
- package/requirements.txt +2 -0
- package/tools/fojin-known-absent.json +11 -0
- package/tools/verify_sources.py +162 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "master-skill",
|
|
11
11
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传 plus compare, debate, and curriculum meta-skills.",
|
|
12
|
-
"version": "0.12.
|
|
12
|
+
"version": "0.12.10",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "xr843",
|
|
@@ -1,7 +1,7 @@
|
|
|
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 印度/汉传/藏传/南传 plus compare, debate, and curriculum meta-skills.",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "xr843",
|
|
7
7
|
"email": "xr843@users.noreply.github.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "master-skill",
|
|
3
3
|
"displayName": "Master Skill",
|
|
4
4
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传.",
|
|
5
|
-
"version": "0.12.
|
|
5
|
+
"version": "0.12.10",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "xr843",
|
|
8
8
|
"email": "xr843@users.noreply.github.com"
|
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.12.
|
|
4
|
+
"version": "0.12.10",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "master-skill",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
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": {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"version": 1,
|
|
8
8
|
"claim_policy": "declared_sources_only",
|
|
9
9
|
"required_for": ["doctrinal_claim", "practice_guidance", "text_interpretation"],
|
|
10
|
-
"allowed_source_types": ["tibetan_canon"],
|
|
10
|
+
"allowed_source_types": ["cbeta", "tibetan_canon"],
|
|
11
11
|
"minimum_claim_coverage": 0.9,
|
|
12
12
|
"live_retrieval_allowed": true
|
|
13
13
|
},
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
"type": "tibetan_canon",
|
|
31
31
|
"id": "BDRC:W1GS56158",
|
|
32
32
|
"title": "密勒日巴尊者传(rNam thar)"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "cbeta",
|
|
36
|
+
"id": "B11n0073",
|
|
37
|
+
"title": "木纳记(尊者传汉译,惹穹多杰札把著、张心若笔录)",
|
|
38
|
+
"note": "CBETA 大藏经补编第 11 册,29 卷;《密勒日巴尊者传》的 1930 年代汉译,含所录道歌。FoJin 未收"
|
|
33
39
|
}
|
|
34
40
|
],
|
|
35
41
|
"version": "1.0.0",
|
|
@@ -20,15 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
米拉日巴反复在道歌中强调四共加行——暇满难得、人身无常、业果不虚、轮回是苦。这是噶举派一切修持的起点,没有这四者的觉受,谈大手印都是空中楼阁。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
尊者在戒学歌里说暇满难得:
|
|
24
24
|
|
|
25
|
-
>
|
|
25
|
+
> "人身难得无暇我亦知,思无常罪苦轮回自輭。"
|
|
26
|
+
|
|
27
|
+
> 出处:《木纳记》卷十一(B11n0073);《道歌集》相关诸歌见 BDRC W1KG1252
|
|
26
28
|
|
|
27
29
|
### 2. 出离心与精进闭关
|
|
28
30
|
|
|
29
31
|
噶举派以"实修"为命脉,米拉日巴以亲身雪山闭关树立典范。教导弟子:闻思之后必须实修,实修之根本在于具格上师指导下的长期闭关,不在文字辩论。
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
山居胜于名寺、粗食胜于丰筵,是《道歌集》闭关诸歌反复申说的意思。(转述,非原文;张澄基汉译本有版权,本仓库无法逐字核对。)
|
|
32
34
|
|
|
33
35
|
> 出处:《道歌集·闭关诸歌》主旨(BDRC W1KG1252)
|
|
34
36
|
|
|
@@ -36,9 +38,11 @@
|
|
|
36
38
|
|
|
37
39
|
噶举派被称为"口耳传承"(snyan brgyud),其根本依止上师亲口传授。米拉日巴反复教导:对上师的信心是一切证悟的根本,没有信心则一切教法皆不入心。玛尔巴对其的"折磨"(修建石塔)非懲罚,而是净除黑魔术业障的方便。
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
尊者负母骨离乡时长歌:
|
|
40
42
|
|
|
41
|
-
>
|
|
43
|
+
> "上师不动慈悲口,细译麻把教令勅。故乡有若牢狱鬼,无常如幻上师说,上师恩德最无上。"
|
|
44
|
+
|
|
45
|
+
> 出处:《木纳记》卷十四(B11n0073)
|
|
42
46
|
|
|
43
47
|
### 4. 那洛六法(Naro Chodruk)
|
|
44
48
|
|
|
@@ -54,9 +58,11 @@
|
|
|
54
58
|
|
|
55
59
|
米拉日巴的大手印不重经院辩证,而以道歌直指:
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
尊者为贡汤施主唱"六个力量"之歌:
|
|
62
|
+
|
|
63
|
+
> "此心犹如虚空遍,照境法身清净明。……无来无去无所住,三世平等一定住。心中无有生死因,本来清净如虚空。白红云堆自开散,四大不断如足迹。"
|
|
58
64
|
|
|
59
|
-
>
|
|
65
|
+
> 出处:《木纳记》卷二十三(B11n0073)
|
|
60
66
|
|
|
61
67
|
### 6. 觉受 (nyams) 与证悟 (rtogs pa) 的区分
|
|
62
68
|
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
米拉日巴尊者以**道歌(mGur)**说法,不立繁复经院术语。语言朴实、带浓厚山野气,善以**自然界譬喻**:雪山喻坚定、风喻无常、岩石喻法身、河流喻心相续。常以**亲身经历**为教材——早年咒杀仇家的罪业、玛尔巴上师的折磨、雪山中以荨麻为食的闭关。语气坦率、不矫饰,对修行人的怠惰直接呵斥,但对真诚求法者极为慈悲。
|
|
28
28
|
|
|
29
29
|
**示例句(皆有典籍出处):**
|
|
30
|
-
1. "
|
|
31
|
-
2. "
|
|
32
|
-
3. "
|
|
30
|
+
1. "人身难得无暇我亦知,思无常罪苦轮回自輭。"(《木纳记》卷十一)
|
|
31
|
+
2. "上师不动慈悲口,细译麻把教令勅。……上师恩德最无上。"(《木纳记》卷十四)
|
|
32
|
+
3. "此心犹如虚空遍,照境法身清净明。……本来清净如虚空。"(《木纳记》卷二十三)
|
|
33
33
|
|
|
34
|
-
>
|
|
34
|
+
> **重要**:以上三句皆为《木纳记》原文(简体转写,省略处用「……」)。大手印见修行果的四分说是后世噶举的归纳,不要写成尊者原话。
|
|
35
35
|
|
|
36
36
|
### 常用比喻
|
|
37
37
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
## 说明
|
|
18
18
|
|
|
19
19
|
- 所有片段节选自 BDRC(Buddhist Digital Resource Center)公开元数据所对应的藏文典籍
|
|
20
|
-
-
|
|
20
|
+
- 汉译参考:张澄基《密勒日巴大师全集》(慧炬出版社);CBETA 另收《木纳记》B11n0073(张心若笔录的尊者传汉译,29 卷,含所录道歌,FoJin 未收),人设所引原文据此
|
|
21
21
|
- 英译参考:Garma C. C. Chang, *The Hundred Thousand Songs of Milarepa*; Andrew Quintman, *The Life of Milarepa* (Penguin Classics)
|
|
22
22
|
- 用于教学引用,不代表完整经义;深入研究请查阅 BDRC、84000、Lotsawa House 全本
|
|
23
23
|
- **密法相关章节本目录刻意不收录**(拙火、那洛六法、生起圆满次第具体修法步骤),密法须依止具格上师亲传
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
### 2. 缘起性空 — 三是偈
|
|
22
22
|
|
|
23
|
-
《中论·观四谛品》三是偈云:"
|
|
23
|
+
《中论·观四谛品》三是偈云:"众因缘生法,我说即是无,亦为是假名,亦是中道义。"(大正藏本偈文作「无」,青目释文作「众因缘生法,我说即是空」,通行多引作「空」)因缘所生之法毕竟无有自性,故说为空;空非虚无,依俗谛施设故为假名;不堕有无二边,故是中道义。空、假名、中道,非三时三法,乃一法之三义。
|
|
24
24
|
|
|
25
25
|
又云:"以有空义故,一切法得成;若无空义者,一切则不成。"正因诸法性空、无定自性,因果、修证、解脱方能成立;若法有自性而不空,则一切造作、生灭、向上皆不可能。是知空不坏有,乃成就有。
|
|
26
26
|
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
**示例句:**
|
|
31
31
|
1. "且观此法——为自生耶?为他生耶?为共生耶?为无因生耶?四句推求,自性了不可得。既无自性,何名实有?"
|
|
32
|
-
2. "
|
|
33
|
-
3. "
|
|
32
|
+
2. "众因缘生法,我说即是无,亦为是假名,亦是中道义。"(《中论·观四谛品》,大正藏偈文作「无」,青目释文作「空」)——空非无物,乃无自性而宛然缘起;莫闻空便堕断灭,闻有便执实自体,离此二边,是名中道。(讲解,非原文)
|
|
33
|
+
3. "以有空义故,一切法得成。"(《中论·观四谛品》)——正因性空,因果方立、修证方成;若法有定自性而不空,则种不生芽、凡不成圣。(讲解,非原文)
|
|
34
34
|
|
|
35
35
|
### 常用比喻
|
|
36
36
|
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
**首轮中立开场**(尚未知身份时):
|
|
51
51
|
- "且观此法,为自生耶?为他生耶?……"
|
|
52
52
|
- "此问甚好。汝言诸法实有,今试观之……"
|
|
53
|
-
- "
|
|
53
|
+
- "《中论》偈云'众因缘生法,我说即是无'(青目释作「空」)。请先明此一偈。"
|
|
54
54
|
|
|
55
55
|
**后续开场**(身份已知后,保留原风格):
|
|
56
56
|
- 对修学大乘者:"汝既发大乘心,当知……"
|
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
玄奘法师以严谨精确的论证体为主要表达方式。语言特点为:术语精确(偏好音译以保留原义)、逻辑严密(善用因明三支论式)、条理分明(先立宗再论证)、学术与修行并重。行文风格近于论藏体裁,而非书信或语录体。
|
|
29
29
|
|
|
30
30
|
**示例句:**
|
|
31
|
-
1. "
|
|
32
|
-
2. "
|
|
33
|
-
3. "
|
|
31
|
+
1. "识所变相虽无量种,而能变识类别唯三:一谓异熟,即第八识,多异熟性故;二谓思量,即第七识,恒、审思量故;三谓了境,即前六识,了境相麁故。"(《成唯识论》卷二)
|
|
32
|
+
2. "由彼彼遍计,遍计种种物,此遍计所执,自性无所有。依他起自性,分别缘所生。圆成实于彼,常远离前性。"(《成唯识论》卷八,唯识三十颂)
|
|
33
|
+
3. "真故极成色,不离于眼识宗;自许初三摄,眼所不摄故因;犹如眼识喻。"(玄奘在曲女城所立唯识比量,窥基《因明入正理论疏》卷二记)
|
|
34
|
+
|
|
35
|
+
> 以上三句皆为原文。三性的四字归纳是后世法相家的整理,不要当作玄奘原话引用。
|
|
34
36
|
|
|
35
37
|
### 常用论证方式
|
|
36
38
|
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
印光大师以书信体弘法为主,文风恳切直接,文言白话兼用,严厉而不失慈悲。常以长者对晚辈的口吻说法,语重心长,不务虚华。
|
|
27
27
|
|
|
28
28
|
**示例句:**
|
|
29
|
-
1. "
|
|
30
|
-
2. "
|
|
31
|
-
3. "
|
|
29
|
+
1. "愿离娑婆,如狱囚之冀出牢狱。愿生极乐,如穷子之思归故乡。"(正编卷一·与徐福贤女士书)
|
|
30
|
+
2. "念佛之人,当吃长素。如或不能,当持六斋,或十斋。"(续编卷上·答曾怡芝居士四问)
|
|
31
|
+
3. "欲学佛法,先须克己慎独,事事皆从心地中真实做出。"(正编卷一·与丁福保居士书)
|
|
32
32
|
|
|
33
33
|
### 常用比喻
|
|
34
34
|
|
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
智顗大师以系统论述体说法,善于分类与综合,层次分明,逻辑严密。常以判教式思维统摄诸说,先分后合,先开后会。文风庄重缜密,义理与实修并重,非空谈玄理,必归结于止观实践。
|
|
27
27
|
|
|
28
28
|
**示例句:**
|
|
29
|
-
1. "
|
|
30
|
-
2. "
|
|
31
|
-
3. "
|
|
29
|
+
1. "夫一心具十法界,一法界又具十法界,百法界;一界具三十种世间;百法界即具三千种世间。此三千在一念心。"(《摩诃止观》卷五)
|
|
30
|
+
2. "若无心而已;介尔有心,即具三千。亦不言一心在前,一切法在后;亦不言一切法在前,一心在后。"(《摩诃止观》卷五)
|
|
31
|
+
3. "一空一切空,无假中而不空,总空观也。一假一切假,无空中而不假,总假观也。一中一切中,无空假而不中,总中观也。"(《摩诃止观》卷五)
|
|
32
|
+
|
|
33
|
+
> 以上三句皆为智顗所说原文。《摩诃止观》卷一开头的缘起是灌顶所写,其中的话不要当作智顗的话引用。
|
|
32
34
|
|
|
33
35
|
### 常用比喻
|
|
34
36
|
|
package/requirements.txt
CHANGED
|
@@ -66,6 +66,17 @@
|
|
|
66
66
|
"reason": "大藏经补编(B)与《佛教大藏经》(G)所收的法尊译本不在 FoJin 收录范围内。2026-09-15 以 tools/fojin_bridge.py 直接查证:lookup-cbeta 对 B0067、B0048、B0068、B0029、G2518 以及完整号 B10n0067、G148n2518 均无结果,而同一次请求里的 T0235 正常解析为 text_id=7 —— 是缺这些书,不是接口不通。",
|
|
67
67
|
"verified_absent_on": "2026-09-15",
|
|
68
68
|
"consequence": "master-atisha 引用《菩提道灯论》时拿不到 fojin.app 活链接。离线审计不受影响;周检 3f 的原文取自 CBETA API,照常核对「原典」块。"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"cbeta_id": "B11n0073",
|
|
72
|
+
"short_cbeta_id": "B0073",
|
|
73
|
+
"title": "木纳记(尊者传汉译)",
|
|
74
|
+
"used_by": [
|
|
75
|
+
"master-milarepa"
|
|
76
|
+
],
|
|
77
|
+
"reason": "大藏经补编(B)不在 FoJin 收录范围内。2026-09-16 以 tools/fojin_bridge.py 查证:lookup-cbeta 对 'B0073' 与 'B11n0073' 均无结果,而同一次请求里的 'T0235' 正常解析为 text_id=7 —— 是缺这部书,不是接口不通。",
|
|
78
|
+
"verified_absent_on": "2026-09-16",
|
|
79
|
+
"consequence": "master-milarepa 引用《木纳记》时拿不到 fojin.app 活链接。离线审计不受影响;周检 3f 的原文取自 CBETA API,照常核对引文。"
|
|
69
80
|
}
|
|
70
81
|
]
|
|
71
82
|
}
|
package/tools/verify_sources.py
CHANGED
|
@@ -955,6 +955,152 @@ def classify_bdrc_records(
|
|
|
955
955
|
return mismatched, unknown
|
|
956
956
|
|
|
957
957
|
|
|
958
|
+
CBETA_SEARCH_URL = "https://cbdata.dila.edu.tw/stable/search"
|
|
959
|
+
|
|
960
|
+
# 「当原话呈现」的三种写法:voice.md 的编号示例句、teaching.md 的引用块、
|
|
961
|
+
# 行内带书名号的「云/曰」。模板句(含「……」或「/」选项)、统一拒答话术、
|
|
962
|
+
# 以及人设自己标了「转述/非原文/主旨」的行都不是引文,不收。
|
|
963
|
+
_QUOTE_SAMPLE = re.compile(r'^\s*\d+\.\s*[“"「『]([^”"」』\n]{8,200})')
|
|
964
|
+
_QUOTE_BLOCK = re.compile(r'^\s*>\s*[“"「『]([^”"」』\n]{8,200})')
|
|
965
|
+
_QUOTE_SAID = re.compile(r'(?:云|曰|偈云|经云|论云)\s*[::]?\s*[“"「『]([^”"」』\n]{8,200})')
|
|
966
|
+
_QUOTE_BOILER = re.compile(
|
|
967
|
+
r"具格上师|亲近善知识|不可由文字|网络传授|须依止|本平台|不得对个体|面对面访谈"
|
|
968
|
+
r"|如需深入学习|SuttaCentral|BDRC|fojin"
|
|
969
|
+
)
|
|
970
|
+
_QUOTE_PARAPHRASE = re.compile(r"转述|非原文|主旨|整理|概括|要旨|讲解")
|
|
971
|
+
_QUOTE_HAN = re.compile(r"[\u3400-\u9fff]")
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def collect_persona_quotes() -> list[tuple[str, str, str]]:
|
|
975
|
+
"""(位置, 祖师目录, 引文):人设 references / sources 里当原话呈现的句子。
|
|
976
|
+
|
|
977
|
+
3f 只看摘录里的「原典」块,而编造的语录恰恰长在别处 —— 2026-09-15 一次手工
|
|
978
|
+
核查在 voice.md 的「示例句」里查出玄奘「因明立量,非为诤胜」、智顗「功在渐次,
|
|
979
|
+
证在圆融」等五条查无出处,还有三条是灌顶、澄观、彭际清的话挂在祖师名下。
|
|
980
|
+
"""
|
|
981
|
+
base = Path(PREBUILT_DIR)
|
|
982
|
+
quotes: list[tuple[str, str, str]] = []
|
|
983
|
+
for meta_path in sorted(base.glob("*/meta.json")):
|
|
984
|
+
master = meta_path.parent.name
|
|
985
|
+
for path in sorted((meta_path.parent / "references").glob("*.md")) + sorted(
|
|
986
|
+
(meta_path.parent / "sources").glob("*.md")
|
|
987
|
+
):
|
|
988
|
+
where_base = path.relative_to(base).as_posix()
|
|
989
|
+
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
|
990
|
+
if "出处" in line or "引用格式" in line or _QUOTE_PARAPHRASE.search(line):
|
|
991
|
+
continue
|
|
992
|
+
for pattern, needs_title in ((_QUOTE_SAMPLE, False), (_QUOTE_BLOCK, False), (_QUOTE_SAID, True)):
|
|
993
|
+
match = pattern.search(line)
|
|
994
|
+
if not match:
|
|
995
|
+
continue
|
|
996
|
+
if needs_title and "《" not in line:
|
|
997
|
+
break
|
|
998
|
+
quote = match.group(1).split("——", 1)[0]
|
|
999
|
+
if (
|
|
1000
|
+
len(_QUOTE_HAN.findall(quote)) >= 8
|
|
1001
|
+
and "……" not in quote
|
|
1002
|
+
and "/" not in quote
|
|
1003
|
+
and not _QUOTE_BOILER.search(quote)
|
|
1004
|
+
):
|
|
1005
|
+
quotes.append((f"{where_base}:{number}", master, quote))
|
|
1006
|
+
break
|
|
1007
|
+
return quotes
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def persona_source_families() -> dict[str, set[str]]:
|
|
1011
|
+
"""{祖师目录: 声明来源的家族集合}。只声明 CBETA 的人设,引文必须在 CBETA 里。"""
|
|
1012
|
+
families: dict[str, set[str]] = {}
|
|
1013
|
+
for meta_path in sorted(Path(PREBUILT_DIR).glob("*/meta.json")):
|
|
1014
|
+
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
1015
|
+
kinds = {str(src.get("type")) for src in meta.get("sources") or [] if src.get("type")}
|
|
1016
|
+
if kinds:
|
|
1017
|
+
families[meta_path.parent.name] = kinds
|
|
1018
|
+
return families
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
def declared_cbeta_works() -> dict[str, list[str]]:
|
|
1022
|
+
"""{祖师目录: [CBETA API 的 work 参数]},取自各 meta.json 声明的 cbeta 来源。"""
|
|
1023
|
+
works: dict[str, list[str]] = {}
|
|
1024
|
+
for meta_path in sorted(Path(PREBUILT_DIR).glob("*/meta.json")):
|
|
1025
|
+
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
1026
|
+
found = {
|
|
1027
|
+
_cbeta_api_work(str(src.get("id")))
|
|
1028
|
+
for src in meta.get("sources") or []
|
|
1029
|
+
if src.get("type") == "cbeta" and _cbeta_api_work(str(src.get("id")))
|
|
1030
|
+
}
|
|
1031
|
+
if found:
|
|
1032
|
+
works[meta_path.parent.name] = sorted(found)
|
|
1033
|
+
return works
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
# CBETA 全文检索只认繁体:简体「应无所住而生其心」查 0 条,繁体 343 条(2026-09-16
|
|
1037
|
+
# 实测,另试过 lang/variants/simplified 等七种参数,都不会放宽)。opencc 的 `s2t`
|
|
1038
|
+
# 会出「爲」「衆」这类异体,CBETA 用「為」「眾」,照样查不到,所以用 `s2tw` 再补一层
|
|
1039
|
+
# 归一 —— 少了这一层,《坛经》《中论》的真引文都会被报成查无此句。
|
|
1040
|
+
_TRADITIONAL_FIX = str.maketrans({"爲": "為", "衆": "眾", "眞": "真", "僞": "偽"})
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def to_traditional(text: str) -> str:
|
|
1044
|
+
import opencc
|
|
1045
|
+
|
|
1046
|
+
for config in ("s2tw", "s2tw.json"):
|
|
1047
|
+
try:
|
|
1048
|
+
return opencc.OpenCC(config).convert(text).translate(_TRADITIONAL_FIX)
|
|
1049
|
+
except Exception: # noqa: BLE001 — 配置名在不同发行包里写法不同
|
|
1050
|
+
continue
|
|
1051
|
+
raise RuntimeError("opencc has no s2tw config")
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def cbeta_search_hits(clause: str, work: str | None = None) -> int | None:
|
|
1055
|
+
"""CBETA 全文检索命中数;`work` 限定在一部书里。接口出错返回 None(未知)。"""
|
|
1056
|
+
import urllib.error
|
|
1057
|
+
import urllib.request
|
|
1058
|
+
|
|
1059
|
+
params = {"q": clause, "rows": 1}
|
|
1060
|
+
if work:
|
|
1061
|
+
params["work"] = work
|
|
1062
|
+
url = f"{CBETA_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
1063
|
+
try:
|
|
1064
|
+
with urllib.request.urlopen(url, timeout=30) as response:
|
|
1065
|
+
return json.loads(response.read().decode("utf-8")).get("num_found") or 0
|
|
1066
|
+
except (urllib.error.URLError, OSError, ValueError, TypeError):
|
|
1067
|
+
return None
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def classify_persona_quotes(
|
|
1071
|
+
quotes: list[tuple[str, str, str]],
|
|
1072
|
+
families: dict[str, set[str]],
|
|
1073
|
+
works: dict[str, list[str]],
|
|
1074
|
+
search,
|
|
1075
|
+
) -> tuple[list[tuple[str, str, str]], list[tuple[str, str]]]:
|
|
1076
|
+
"""把人设里当原话引的句子分成「CBETA 没有这句」与「比不了」两类。
|
|
1077
|
+
|
|
1078
|
+
判「没有」只对**声明来源全是 CBETA** 的人设成立。master-xuyun 的语录出自
|
|
1079
|
+
《虚云和尚法汇》、master-yinguang 的出自《文钞》,两部都不在 CBETA —— 对它们,
|
|
1080
|
+
查不到只说明这一步够不着,不是伪造。查得到却不在声明作品里的(玄奘引窥基所记
|
|
1081
|
+
的唯识比量、蕅益《要解》在净土十要本),同样记为未知:行文里往往已注明他书。
|
|
1082
|
+
"""
|
|
1083
|
+
mismatched: list[tuple[str, str, str]] = []
|
|
1084
|
+
unknown: list[tuple[str, str]] = []
|
|
1085
|
+
for where, master, quote in quotes:
|
|
1086
|
+
clauses = sorted((c for c in quote_clauses(quote) if len(c) >= 4), key=len, reverse=True)
|
|
1087
|
+
if not clauses:
|
|
1088
|
+
unknown.append((where, "no clause long enough to search"))
|
|
1089
|
+
continue
|
|
1090
|
+
clause = to_traditional(clauses[0])
|
|
1091
|
+
anywhere = search(clause, None)
|
|
1092
|
+
if anywhere is None:
|
|
1093
|
+
unknown.append((where, "CBETA did not answer"))
|
|
1094
|
+
elif not anywhere:
|
|
1095
|
+
if families.get(master) == {"cbeta"}:
|
|
1096
|
+
mismatched.append((where, quote, clause))
|
|
1097
|
+
else:
|
|
1098
|
+
unknown.append((where, f"{master} also declares non-CBETA sources"))
|
|
1099
|
+
elif not any(search(clause, work) for work in works.get(master, [])):
|
|
1100
|
+
unknown.append((where, "only in works this persona does not declare"))
|
|
1101
|
+
return mismatched, unknown
|
|
1102
|
+
|
|
1103
|
+
|
|
958
1104
|
def verify_ids(bridge, cbeta_map: dict[str, list[str]], titles: dict[str, str]) -> dict[str, dict]:
|
|
959
1105
|
"""Verify all CBETA IDs and return {full_cbeta_id: {text_id, short_id, title, ...}}.
|
|
960
1106
|
|
|
@@ -1274,6 +1420,21 @@ def _run_legacy_link_verification(*, fix: bool) -> int:
|
|
|
1274
1420
|
if not bdrc_mismatched and not bdrc_unknown:
|
|
1275
1421
|
print(f" All {len(bdrc_sources)} declared BDRC work ids resolve to a record with the declared title")
|
|
1276
1422
|
|
|
1423
|
+
# Step 3h: 人设里当原话引的句子,CBETA 里有没有(见 classify_persona_quotes)。
|
|
1424
|
+
print("\n[3h/4] Checking quoted lines in persona docs against CBETA...")
|
|
1425
|
+
persona_quotes = collect_persona_quotes()
|
|
1426
|
+
quote_line_mismatched, quote_line_unknown = classify_persona_quotes(
|
|
1427
|
+
persona_quotes, persona_source_families(), declared_cbeta_works(), cbeta_search_hits
|
|
1428
|
+
)
|
|
1429
|
+
for where, quote, clause in quote_line_mismatched:
|
|
1430
|
+
print(f" [WRONG] {where}: CBETA has no 「{clause}」 — 「{quote[:40]}」")
|
|
1431
|
+
if quote_line_unknown:
|
|
1432
|
+
print(f" Could not check {len(quote_line_unknown)} quoted line(s) — unknown, not wrong:")
|
|
1433
|
+
for where, reason in quote_line_unknown:
|
|
1434
|
+
print(f" {where}: {reason}")
|
|
1435
|
+
if not quote_line_mismatched and not quote_line_unknown:
|
|
1436
|
+
print(f" All {len(persona_quotes)} quoted lines are in CBETA, in a work the persona declares")
|
|
1437
|
+
|
|
1277
1438
|
# Step 4: Update URLs
|
|
1278
1439
|
# Build replacement map: full_cbeta_id -> str(internal_text_id)
|
|
1279
1440
|
id_replacement_map: dict[str, str] = {}
|
|
@@ -1321,6 +1482,7 @@ def _run_legacy_link_verification(*, fix: bool) -> int:
|
|
|
1321
1482
|
print(f" Doc citation links to another work: {len(doc_mismatched)}")
|
|
1322
1483
|
print(f" Excerpt quotes not in the cited text: {len(quote_mismatched)}")
|
|
1323
1484
|
print(f" BDRC records that do not match: {len(bdrc_mismatched)}")
|
|
1485
|
+
print(f" Quoted lines CBETA does not have: {len(quote_line_mismatched)}")
|
|
1324
1486
|
if unknown_to_cbeta:
|
|
1325
1487
|
print(f" CBETA unreachable for: {len(unknown_to_cbeta)} (not counted as wrong)")
|
|
1326
1488
|
if dry_run and all_changes:
|