master-skill 0.12.9 → 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.
@@ -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.9",
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.9",
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.9",
5
+ "version": "0.12.10",
6
6
  "author": {
7
7
  "name": "xr843",
8
8
  "email": "xr843@users.noreply.github.com"
@@ -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.9",
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.9",
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": {
@@ -45,7 +45,7 @@
45
45
 
46
46
  > "诸比丘,何为三增上学?增上戒学、增上心学、增上慧学。"
47
47
 
48
- > 出处:【SC: AN 3.88 / Sikkhā Sutta】(SuttaCentral)
48
+ > 出处:【SC: AN 3.88 / Tatiyasikkhā Sutta】(SuttaCentral)
49
49
 
50
50
  ### 2. 四十种业处(kammaṭṭhāna) — 培育止禅
51
51
 
@@ -12,6 +12,8 @@ sources:
12
12
  - title: 密勒日巴尊者传
13
13
  bdrc_id: W1GS56158
14
14
  tibetan_title: rNam thar
15
+ - title: 木纳记(尊者传汉译)
16
+ cbeta_id: B11n0073
15
17
  citation_format: "【《{title}》{section}】(BDRC: {bdrc_id})"
16
18
  verified_by: xr843
17
19
  verified_at: 2026-05-02
@@ -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
- > 出处:《道歌集·与亲妹白达之歌》(BDRC W1KG1252)
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
- > 出处:《道歌集·上师瑜伽诸歌》主旨(BDRC W1KG1252)
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
- > 出处:《道歌集·见地诸歌》主旨(BDRC W1KG1252
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
  - **密法相关章节本目录刻意不收录**(拙火、那洛六法、生起圆满次第具体修法步骤),密法须依止具格上师亲传
@@ -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
 
package/requirements.txt CHANGED
@@ -4,3 +4,5 @@ requests>=2.32.5,<2.33; python_version < "3.10"
4
4
  requests>=2.34.2; python_version >= "3.10"
5
5
  pypinyin>=0.55.0
6
6
  pyyaml>=6.0.3
7
+ # 3h 把人设引文转成繁体再查 CBETA:检索只认繁体,简体一律 0 条。
8
+ opencc-python-reimplemented>=0.1.7
@@ -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
  }
@@ -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: