master-skill 0.12.11 → 0.12.12

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.11",
12
+ "version": "0.12.12",
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.11",
4
+ "version": "0.12.12",
5
5
  "author": {
6
6
  "name": "xr843",
7
7
  "email": "xr843@users.noreply.github.com"
@@ -9,6 +9,10 @@
9
9
  "homepage": "https://github.com/xr843/Master-skill",
10
10
  "repository": "https://github.com/xr843/Master-skill",
11
11
  "license": "MIT",
12
+ "skills": [
13
+ "./",
14
+ "./prebuilt/"
15
+ ],
12
16
  "keywords": [
13
17
  "buddhism",
14
18
  "chinese-buddhism",
package/.codex/INSTALL.md CHANGED
@@ -3,35 +3,42 @@
3
3
  ## Quick Install
4
4
 
5
5
  ```bash
6
- # Clone the repository
7
6
  git clone https://github.com/xr843/Master-skill.git ~/.codex/master-skill
8
-
9
- # Create skills symlink
10
- ln -sf ~/.codex/master-skill/prebuilt ~/.agents/skills/master-skill
11
- ln -sf ~/.codex/master-skill/SKILL.md ~/.agents/skills/create-master/SKILL.md
7
+ mkdir -p ~/.agents/skills
8
+ ln -sfn ~/.codex/master-skill/prebuilt ~/.agents/skills/master-skill
9
+ ln -sfn ~/.codex/master-skill ~/.agents/skills/create-master
12
10
  ```
13
11
 
14
- Restart Codex to discover the new skills.
12
+ Restart Codex. It should list 20 skills: 15 personas, 4 teaching modes, and
13
+ `create-master`. Codex names a skill found under a linked directory after that
14
+ directory, so they appear as `master-skill:master-huineng`,
15
+ `master-skill:compare-masters`, and so on.
16
+
17
+ Measured with Codex CLI 0.153.4 on Linux, isolated HOME, 2026-09-16
18
+ (`codex debug prompt-input` shows the skills the model is given):
19
+
20
+ - Without the `mkdir -p`, both `ln` commands fail on a machine where
21
+ `~/.agents/skills` does not exist yet.
22
+ - `create-master` must be a link to the whole checkout. A link to the root
23
+ `SKILL.md` alone is a file symlink, and Codex skips it; the generator also
24
+ needs the `prompts/` and `references/` beside it.
25
+ - `npx master-skill install` installs to `~/.claude/skills/`, which Codex does
26
+ not read.
15
27
 
16
28
  ## Windows (PowerShell)
17
29
 
30
+ Not re-verified; mirrors the steps above.
31
+
18
32
  ```powershell
19
33
  git clone https://github.com/xr843/Master-skill.git "$env:USERPROFILE\.codex\master-skill"
34
+ New-Item -ItemType Directory -Force "$env:USERPROFILE\.agents\skills" | Out-Null
20
35
  cmd /c mklink /J "$env:USERPROFILE\.agents\skills\master-skill" "$env:USERPROFILE\.codex\master-skill\prebuilt"
36
+ cmd /c mklink /J "$env:USERPROFILE\.agents\skills\create-master" "$env:USERPROFILE\.codex\master-skill"
21
37
  ```
22
38
 
23
39
  ## Available Skills After Install
24
40
 
25
- - `/xuanzang` Xuanzang (Yogacara)
26
- - `/kumarajiva` — Kumarajiva (Madhyamaka)
27
- - `/huineng` — Huineng (Chan/Zen)
28
- - `/zhiyi` — Zhiyi (Tiantai)
29
- - `/fazang` — Fazang (Huayan)
30
- - `/yinguang` — Yinguang (Pure Land)
31
- - `/ouyi` — Ouyi (Tiantai-Pure Land)
32
- - `/xuyun` — Xuyun (Chan, Five Schools)
33
- - `/compare-masters` — Multi-tradition comparison
34
- - `/create-master` — Generate new master
41
+ Run `npx master-skill list` for the current list with descriptions.
35
42
 
36
43
  ## Tool Mapping
37
44
 
@@ -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.11",
5
+ "version": "0.12.12",
6
6
  "author": {
7
7
  "name": "xr843",
8
8
  "email": "xr843@users.noreply.github.com"
@@ -2,36 +2,49 @@
2
2
 
3
3
  ## Quick Install
4
4
 
5
- Add to your `opencode.json`:
5
+ ```bash
6
+ npx master-skill install --all
7
+ ```
8
+
9
+ OpenCode reads `~/.claude/skills/` without any configuration, and that is where
10
+ this installs. `opencode debug skill` should list 20 skills: 15 personas, 4
11
+ teaching modes, and `create-master`.
12
+
13
+ ## From a Checkout
14
+
15
+ Point `skills.paths` in `opencode.json` at the checkout's `prebuilt/`:
6
16
 
7
17
  ```json
8
18
  {
9
- "plugin": ["master-skill@git+https://github.com/xr843/Master-skill.git"]
19
+ "skills": { "paths": ["/absolute/path/to/Master-skill/prebuilt"] }
10
20
  }
11
21
  ```
12
22
 
13
- OpenCode will automatically install and register all skills.
23
+ That gives the 19 skills under `prebuilt/`. `create-master` lives at the
24
+ repository root, outside it.
14
25
 
15
26
  ## Pin a Version
16
27
 
28
+ ```bash
29
+ npx master-skill@<version> install --all
30
+ ```
31
+
32
+ ## Not a Plugin
33
+
34
+ Earlier versions of this page said to add
35
+
17
36
  ```json
18
- {
19
- "plugin": ["master-skill@git+https://github.com/xr843/Master-skill.git#v0.3.0"]
20
- }
37
+ { "plugin": ["master-skill@git+https://github.com/xr843/Master-skill.git"] }
21
38
  ```
22
39
 
40
+ OpenCode plugins are JavaScript modules, and this repository ships none. With
41
+ that entry `opencode debug skill` lists none of these skills.
42
+
43
+ Measured with OpenCode 1.18.13 on Linux, isolated HOME, 2026-09-16.
44
+
23
45
  ## Available Skills After Install
24
46
 
25
- - `/xuanzang` Xuanzang (Yogacara)
26
- - `/kumarajiva` — Kumarajiva (Madhyamaka)
27
- - `/huineng` — Huineng (Chan/Zen)
28
- - `/zhiyi` — Zhiyi (Tiantai)
29
- - `/fazang` — Fazang (Huayan)
30
- - `/yinguang` — Yinguang (Pure Land)
31
- - `/ouyi` — Ouyi (Tiantai-Pure Land)
32
- - `/xuyun` — Xuyun (Chan, Five Schools)
33
- - `/compare-masters` — Multi-tradition comparison
34
- - `/create-master` — Generate new master
47
+ Run `npx master-skill list` for the current list with descriptions.
35
48
 
36
49
  ## Tool Mapping
37
50
 
package/README.md CHANGED
@@ -139,7 +139,7 @@ Master-skill 是由 [FoJin](https://fojin.app) 驱动的佛教 AI 祖师人格
139
139
  - **HARD-GATE 铁律**:`/create-master` 与预置法师内置红线——教义断言、修行指导与文本解释必须引用该 persona 声明的来源(CBETA / BDRC / Toh / SuttaCentral / PTS / 合规编纂开示),不得捏造来源 ID,不得为虚构人物建角色
140
140
  - **二阶段独立审查**:生成管线在写入前强制经过"教义准确性 → 风格一致性"两轮独立审查,FAIL 自动修复最多 2 轮
141
141
  - **自动化保真度测试**:211 条夹具(每位祖师 10+ 条,`compare-masters` 元技能 18 条)检查关键词与引用覆盖,每条评分回答另经离线引文审计;CI 在每个 PR 与 main 上做结构校验(dry-run);实跑评分支持 Anthropic / DeepSeek / Gemini(`--provider`),需要对应的 API key,作为本地/发版前手动步骤执行——最近一次全量运行与逐条裁定见[下文](#保真度评测当前数据)
142
- - **多平台统一插件**:Claude Code、Cursor、Codex CLI、OpenCode、Gemini CLI 共用一份 `prebuilt/`,session-start hook 跨平台注入法师列表
142
+ - **多平台共用一份 `prebuilt/`**:Claude Code、Cursor、Codex CLI、OpenCode、Gemini CLI 各有装法(见 [docs/install.md](docs/install.md),Codex / OpenCode / Gemini 三端均为实测过的步骤)
143
143
  - **NPX 一键安装**:`npx master-skill install master-zhiyi` 直接部署到 Claude Code
144
144
  - **离线工具链**:`scripts/cite.py`(CBETA 引用查询)、`scripts/query.py`(离线语义检索)、`scripts/validate.py`(frontmatter linter)
145
145
  - **FoJin 数据桥**:接入 [fojin.app](https://fojin.app) 的 10K+ 篇文本、678K+ 条语义向量与 110K+ 实体的知识图谱(另登记 600+ 个数据源)
@@ -190,7 +190,7 @@ npx master-skill doctor # 检查本地安装与运行路径
190
190
  npx master-skill update --all # 升级:重装全部并清掉旧文件
191
191
  ```
192
192
 
193
- 装好后在对话里直接调 `/master-huineng`、`/compare-masters` 等。
193
+ 装好后在对话里直接调 `/master-huineng`、`/compare-masters` 等。npx 装到 `~/.claude/skills/`,Claude Code 与 OpenCode 读取这里;**Codex CLI 与 Gemini CLI 不读**,请按 docs/install.md 的对应小节安装。
194
194
 
195
195
  > 五端安装细节(Claude Code 插件 / Cursor / OpenCode / Codex CLI / Gemini CLI)、
196
196
  > 全局安装、教学模式用法、`/create-master` 自定义生成
package/README_EN.md CHANGED
@@ -157,7 +157,7 @@ This project is built out of respect for Buddhist traditions. All content is gen
157
157
  - **HARD-GATE discipline**: Both `/create-master` and every prebuilt master require doctrinal claims, practice guidance, and text interpretation to cite that persona's declared sources (CBETA / BDRC / Toh / SuttaCentral / PTS / compliant compiled teachings); fabricated source IDs and fictional personas are forbidden
158
158
  - **Two-stage independent review**: The generation pipeline forces a "doctrinal accuracy → voice consistency" review before write; FAIL triggers up to 2 rounds of automatic repair
159
159
  - **Automated fidelity tests**: 211 fixtures (10+ per master, 18 for the `compare-masters` meta-skill) check keyword and citation coverage, and every graded answer also goes through the offline citation audit; CI runs a structural dry-run on every PR and on `main`; graded runs support Anthropic / DeepSeek / Gemini (`--provider`) with the matching API key, as a manual local/pre-release step — the latest full run and its case-by-case adjudication are [below](#fidelity-evaluation-current-data)
160
- - **Unified multi-platform plugin**: Claude Code, Cursor, Codex CLI, OpenCode, and Gemini CLI share one `prebuilt/` tree, with a session-start hook injecting the master list on every platform
160
+ - **One `prebuilt/` tree across platforms**: Claude Code, Cursor, Codex CLI, OpenCode, and Gemini CLI each install it differently (see [docs/install.en.md](docs/install.en.md); the Codex, OpenCode, and Gemini steps are measured)
161
161
  - **NPX one-shot install**: `npx master-skill install master-zhiyi` drops skills straight into Claude Code
162
162
  - **Offline toolchain**: `scripts/cite.py` (CBETA lookup), `scripts/query.py` (offline semantic search), `scripts/validate.py` (frontmatter linter)
163
163
  - **FoJin data bridge**: Connected to [fojin.app](https://fojin.app) — 10K+ texts, 678K+ semantic embeddings, a knowledge graph of 110K+ entities, and 600+ registered data sources
@@ -208,7 +208,7 @@ npx master-skill doctor # check the local install and runtime pa
208
208
  npx master-skill update --all # upgrade: reinstall everything, clearing stale files
209
209
  ```
210
210
 
211
- Once installed, invoke `/master-huineng`, `/compare-masters`, etc. directly in chat.
211
+ Once installed, invoke `/master-huineng`, `/compare-masters`, etc. directly in chat. npx installs to `~/.claude/skills/`, which Claude Code and OpenCode read; **Codex CLI and Gemini CLI do not**, so use their sections in docs/install.en.md.
212
212
 
213
213
  > Per-platform setup (Claude Code plugin / Cursor / OpenCode / Codex CLI / Gemini CLI),
214
214
  > global install, teaching-mode usage, and `/create-master`
package/SKILL.md CHANGED
@@ -94,7 +94,7 @@ API 故障 / 超时 / 数据阈值 / 引用规则细节 → `references/workflow
94
94
 
95
95
  将审查通过的名称、传承、宗派、时代、语言、`teaching_content`、`voice_content`、`sources` 与同一 `citation_contract` 写入 `generated-master.json`,再运行 `${CLAUDE_SKILL_DIR}/tools/master_builder.py --spec generated-master.json --output "${CLAUDE_SKILL_DIR}/masters/"`。生成器统一写入 `masters/master-{slug}/`,且 `SKILL.md` 的 name 为 `master-{slug}`;随后运行 `${CLAUDE_SKILL_DIR}/tools/verify_sources.py --final-check "${CLAUDE_SKILL_DIR}/masters/master-{slug}/"`。该终验离线检查四个必需文件、目录/name 一致性,以及 `meta.json` 的来源清单、家族 ID、声明归属和 contract;它不解析 `teaching.md` 自由文本,也不保证外部站点可达。
96
96
 
97
- OpenClaw / Claude Code 注册路径 → `references/workflow-details.md` §Step 5。
97
+ 终验通过后运行 `${CLAUDE_SKILL_DIR}/tools/master_builder.py --register "${CLAUDE_SKILL_DIR}/masters/master-{slug}"`:Claude Code 只加载 `~/.claude/skills/<名字>/SKILL.md`,不扫 `masters/`,不注册就无法用 `/master-{slug}` 调用。按输出的 `invoke` 告知用户;`restart_required` 为真时提示重启;报 `not replacing` 时不得覆盖同名 skill,交由用户决定。细节与 OpenClaw 注册 → `references/workflow-details.md` §角色注册(按运行环境)。
98
98
 
99
99
  ## 追加材料、纠正、管理命令
100
100
 
@@ -102,7 +102,7 @@ OpenClaw / Claude Code 注册路径 → `references/workflow-details.md` §Step
102
102
  - **纠正模式**:用户说"他不会这样说话/他应该更严厉" → 加载 `prompts/correction_handler.md`;以 `## Correction` 块追加到 teaching.md / voice.md 末尾;patch 递增。
103
103
  - **管理命令**:`/list-masters`(列出所有,标 `[预置]`/`[自定义]`)、`/master-rollback <slug> <version>`(回滚,自动归档当前)、`/delete-master <slug>`(删除,预置不可删,需二次确认)。
104
104
 
105
- 冲突处理策略、版本号细节、用户确认文案 → `references/workflow-details.md` §追加纠正管理。
105
+ 冲突处理策略、版本号细节、用户确认文案 → `references/workflow-details.md` §追加材料、纠正、管理命令细则。
106
106
 
107
107
  ## 执行优先级(运行时)
108
108
 
package/bin/cli.mjs CHANGED
@@ -435,6 +435,101 @@ function cmdUninstall(names) {
435
435
  return failed;
436
436
  }
437
437
 
438
+ // Every file `install` copies for this skill, relative to its install dir.
439
+ // Mirrors cmdInstall: a persona or mode copies its whole source directory; the
440
+ // generator copies its bundle_paths. `masters/` holds the user's generated
441
+ // personas and `__pycache__` is written by running the tools, so neither is
442
+ // part of what the package installed.
443
+ function expectedInstallFiles(skill) {
444
+ const src = path.join(PACKAGE_ROOT, skill.source);
445
+ const roots =
446
+ skill.kind === "generator"
447
+ ? skill.bundle_paths.filter((bundlePath) => bundlePath !== "masters")
448
+ : ["."];
449
+ const files = [];
450
+ const walk = (rel) => {
451
+ const abs = path.join(src, rel);
452
+ if (!fs.existsSync(abs)) return;
453
+ if (fs.statSync(abs).isDirectory()) {
454
+ if (path.basename(abs) === "__pycache__") return;
455
+ for (const entry of fs.readdirSync(abs)) {
456
+ walk(rel === "." ? entry : path.join(rel, entry));
457
+ }
458
+ } else if (!abs.endsWith(".pyc")) {
459
+ files.push(rel);
460
+ }
461
+ };
462
+ for (const root of roots) walk(root);
463
+ return files;
464
+ }
465
+
466
+ // What is actually under the skills directory. Until 2026-09-17 doctor only
467
+ // checked the package's own sources, and reported "ok" (exit 0) with an
468
+ // installed SKILL.md deleted, a persona's references/ gone, the generator's
469
+ // tools/ gone, an install from an older version — and with nothing installed.
470
+ // A skill that is simply not installed is not a problem: installing one master
471
+ // is a normal choice.
472
+ function installedProblems() {
473
+ const problems = [];
474
+ for (const skill of catalogSkills()) {
475
+ const dest = path.join(SKILLS_DIR, skill.install_dir);
476
+ if (!fs.existsSync(dest)) continue;
477
+ if (!fs.existsSync(path.join(dest, "SKILL.md"))) {
478
+ problems.push({
479
+ code: "installed-missing-skill-md",
480
+ name: skill.name,
481
+ message: `${skill.name} is installed without SKILL.md — run: master-skill install ${skill.name}`,
482
+ });
483
+ continue;
484
+ }
485
+ const missing = [];
486
+ const changed = [];
487
+ for (const rel of expectedInstallFiles(skill)) {
488
+ const installed = path.join(dest, rel);
489
+ if (!fs.existsSync(installed)) {
490
+ missing.push(rel);
491
+ } else if (
492
+ !fs.readFileSync(installed).equals(
493
+ fs.readFileSync(path.join(PACKAGE_ROOT, skill.source, rel))
494
+ )
495
+ ) {
496
+ changed.push(rel);
497
+ }
498
+ }
499
+ if (missing.length) {
500
+ problems.push({
501
+ code: "installed-incomplete",
502
+ name: skill.name,
503
+ message: `${skill.name} is missing ${missing.length} installed file(s), e.g. ${missing[0]} — run: master-skill install ${skill.name}`,
504
+ });
505
+ }
506
+ if (changed.length) {
507
+ problems.push({
508
+ code: "installed-outdated",
509
+ name: skill.name,
510
+ message: `${skill.name} differs from package ${pkgVersion()} in ${changed.length} file(s), e.g. ${changed[0]} — run: master-skill update --all`,
511
+ });
512
+ }
513
+ }
514
+
515
+ // Personas registered by create-master are links into create-master/masters/;
516
+ // uninstalling the generator leaves them pointing at nothing.
517
+ if (fs.existsSync(SKILLS_DIR)) {
518
+ for (const entry of fs.readdirSync(SKILLS_DIR, { withFileTypes: true })) {
519
+ if (!entry.isSymbolicLink()) continue;
520
+ const link = path.join(SKILLS_DIR, entry.name);
521
+ if (!fs.existsSync(link)) {
522
+ problems.push({
523
+ code: "dangling-link",
524
+ name: entry.name,
525
+ message: `${entry.name} links to ${fs.readlinkSync(link)}, which no longer exists`,
526
+ });
527
+ }
528
+ }
529
+ }
530
+ return problems;
531
+ }
532
+
438
533
  function doctorData() {
439
534
  const masters = availableMasters();
440
535
  const installed = installedSkillDirs();
@@ -453,6 +548,7 @@ function doctorData() {
453
548
  name: skill.name,
454
549
  message: `${skill.name} is missing SKILL.md`,
455
550
  }));
551
+ problems.push(...installedProblems());
456
552
 
457
553
  return {
458
554
  packageVersion: pkgVersion(),
@@ -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.11",
4
+ "version": "0.12.12",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -25,13 +25,24 @@ import json
25
25
  import os
26
26
  import re
27
27
  import sys
28
+ import unicodedata
28
29
  from pathlib import Path
29
30
 
30
- # Whitelist: CJK Unified, ASCII alphanumerics, fullwidth parens, space, · _ ( ) -
31
- # Everything else backticks, dollars, quotes, slashes, control characters
32
- # is dropped. An attacker who lands a malicious SKILL.md (or a contributor with
33
- # a typo) must not be able to reach the system prompt through it.
34
- _ALLOWED = re.compile(r"[^一-鿿0-9A-Za-z _\-·()()]", re.UNICODE)
31
+ # Whitelist: CJK Unified, ASCII alphanumerics, Latin letters with diacritics,
32
+ # fullwidth parens and solidus, space, · _ ( ) -
33
+ # Everything else backticks, dollars, quotes, ASCII slashes, control, format
34
+ # and bidi characters is dropped. An attacker who lands a malicious SKILL.md
35
+ # (or a contributor with a typo) must not be able to reach the system prompt
36
+ # through it.
37
+ #
38
+ # The Latin ranges are letters only: À-Ö Ø-ö ø-ÿ (Latin-1 without × and ÷),
39
+ # Latin Extended-A, and Latin Extended Additional. Until 2026-09-16 they were
40
+ # absent, and five of fifteen shipped lineages reached the model altered:
41
+ # "(Mahāvihāra)" as "(Mahvihra)", and "三论宗/中观" as the single made-up term
42
+ # "三论宗中观" because the slash was deleted rather than kept. An ASCII slash is
43
+ # still never emitted — it could read as a slash command — so it becomes the
44
+ # fullwidth "/", which keeps the "A or B" meaning.
45
+ _ALLOWED = re.compile(r"[^一-鿿0-9A-Za-zÀ-ÖØ-öø-ſḀ-ỿ _\-·()()/]", re.UNICODE)
35
46
  _CONTROL = re.compile(r"[\x00-\x1f\x7f-\x9f]")
36
47
  _WHITESPACE = re.compile(r"\s+")
37
48
 
@@ -61,8 +72,10 @@ _LINEAGE_LINE = re.compile(r"^lineage:[ \t]*(.*)$", re.MULTILINE)
61
72
 
62
73
  def sanitize_lineage(raw: str) -> str:
63
74
  """Normalize one raw `lineage:` frontmatter value for prompt splicing."""
64
- text = _CONTROL.sub("", raw or "")
65
- text = _ALLOWED.sub("", text)
75
+ # NFC first: a decomposed "ā" (a + U+0304) would otherwise lose its
76
+ # combining macron and silently become "a".
77
+ text = _CONTROL.sub("", unicodedata.normalize("NFC", raw or ""))
78
+ text = _ALLOWED.sub("", text.replace("/", "/"))
66
79
  text = _WHITESPACE.sub(" ", text).strip()
67
80
  return text[:MAX_LINEAGE_CHARS]
68
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "master-skill",
3
- "version": "0.12.11",
3
+ "version": "0.12.12",
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": {
@@ -16,7 +16,7 @@
16
16
  "validate:versions": "python3 scripts/check-manifest-versions.py",
17
17
  "test:hook": "bash hooks/tests/test_session_start.sh && bash hooks/tests/test_run_hook.sh && bash hooks/tests/test_run_hook_cmd.sh",
18
18
  "test:cli": "node --test tests/cli.test.mjs",
19
- "test": "python3 scripts/check-gate-liveness.py && python3 scripts/validate.py --strict && python3 scripts/validate-citation-references.py && python3 scripts/validate-citation-templates.py && python3 scripts/validate-self-audit-sources.py && python3 scripts/validate-citation-contract.py && python3 scripts/validate-cross-critique.py && python3 scripts/validate-lore-triggers-content.py --strict && python3 scripts/validate-quote-attribution.py && python3 scripts/validate-promptfoo-configs.py && python3 scripts/validate-fidelity.py && python3 scripts/validate-persona-fidelity.py && python3 scripts/check-manifest-versions.py && python3 scripts/validate-routing.py && python3 scripts/validate-fixture-terms.py && python3 scripts/verify-adjudication.py && python3 scripts/test-fidelity.py --all --dry-run && node --test tests/cli.test.mjs && python3 -m pytest tests/ scripts/tests/ -q",
19
+ "test": "python3 scripts/check-gate-liveness.py && python3 scripts/validate.py --strict && python3 scripts/validate-citation-references.py && python3 scripts/validate-citation-templates.py && python3 scripts/validate-self-audit-sources.py && python3 scripts/validate-citation-contract.py && python3 scripts/validate-cross-critique.py && python3 scripts/validate-lore-triggers-content.py --strict && python3 scripts/validate-quote-attribution.py && python3 scripts/validate-section-references.py && python3 scripts/validate-promptfoo-configs.py && python3 scripts/validate-fidelity.py && python3 scripts/validate-persona-fidelity.py && python3 scripts/check-manifest-versions.py && python3 scripts/validate-routing.py && python3 scripts/validate-fixture-terms.py && python3 scripts/verify-adjudication.py && python3 scripts/test-fidelity.py --all --dry-run && node --test tests/cli.test.mjs && python3 -m pytest tests/ scripts/tests/ -q",
20
20
  "test:smoke": "python3 scripts/test-fidelity.py --master yinguang --max-tests 1",
21
21
  "prepack": "node bin/cli.mjs list"
22
22
  },
@@ -29,15 +29,15 @@ verified_at: 2026-05-02
29
29
 
30
30
  用户问题类型 →
31
31
  - **正念 / 觉知 / 看自己的心**(sati / awareness / mindfulness)
32
- → 读 `sources/teachings-excerpts.md` §正念与觉知 + `references/teaching.md` §心的训练
32
+ → 读 `sources/teachings-excerpts.md` §正念与觉知、§心的训练 + `references/teaching.md` §四念处与正念
33
33
  - **放下 / 执取 / 痛苦的根源**(letting go / clinging / dukkha)
34
- → 读 `sources/teachings-excerpts.md` §放下 + `references/teaching.md` §苦与放下
34
+ → 读 `sources/teachings-excerpts.md` §放下 + `references/teaching.md` §放下
35
35
  - **三法印 / 无常 / 无我**(anicca / dukkha / anatta)
36
36
  → 读 `sources/sutta-excerpts.md` §三法印 + `references/teaching.md` §三法印
37
37
  - **禅修方法 / 出入息念 / 妄念多**(anapanasati / 散乱)
38
- → 读 `sources/teachings-excerpts.md` §禅修与出入息念 + `references/teaching.md` §禅那与毗婆舍那
38
+ → 读 `sources/teachings-excerpts.md` §禅修与出入息念 + `references/teaching.md` §出入息念
39
39
  - **戒律 / 出家生活 / 杜多行**(vinaya / dhutanga / 头陀)
40
- → 读 `references/teaching.md` §戒与森林生活
40
+ → 读 `references/teaching.md` §戒定慧三学、§杜多行 + `sources/teachings-excerpts.md` §日常生活与中道
41
41
  - **风格对话**("想和阿姜查交流"/角色扮演)
42
42
  → 读 `references/voice.md` 建立人格(**内化即可,勿向用户复述此步**),再按上述分类响应
43
43
  - **离线摘录覆盖不到已声明来源的所需位置**(具体卷次 / 已声明来源的章节未收录 / `sources/` 检索为空)
@@ -9,7 +9,7 @@
9
9
 
10
10
  ### 五蕴非我经(Anattalakkhaṇa Sutta)
11
11
 
12
- 佛陀第二次说法对象——五位最初弟子。佛说:"比丘们,色(rūpa)非我;受(vedanā)非我;想(saññā)非我;行(saṅkhārā)非我;识(viññāṇa)非我。何以故?若色是我,色不应病;若色是我,应能令色'如是、不如是'。诸比丘,色是无常、苦、变易法,故非我、非我所、非我体。受、想、行、识亦复如是。"
12
+ 佛陀第二次说法对象——五位最初弟子。经文要旨(非逐字译文):色(rūpa)非我——若色是我,色就不会招致病苦,也能令色如是、不如是;正因色非我,色才招致病苦,也不能令色如是、不如是。受(vedanā)、想(saññā)、行(saṅkhārā)、识(viññāṇa)亦然。佛继而以问答引导五比丘:色是常还是无常?比丘答无常。无常者是苦是乐?答苦。无常、苦、变易之法,可视之为此是我所、此是我、此是我体吗?答不可。故一切色,无论过去未来现在、内外、粗细、劣胜、远近,皆应以正慧如实观为此非我所、此非我、此非我体;受、想、行、识亦然。
13
13
 
14
14
  听此说法已,五比丘漏尽,证阿罗汉果。
15
15
 
@@ -35,7 +35,7 @@
35
35
 
36
36
  ### 念处经 / 大念处经(Satipaṭṭhāna / Mahāsatipaṭṭhāna Sutta)
37
37
 
38
- 佛说:"诸比丘,此一行道,能令众生清净、超越愁悲、灭除苦忧、得正理、证涅槃,所谓四念处。何为四?比丘于身观身、于受观受、于心观心、于法观法,热忱、正知、正念,远离世间贪忧。"
38
+ 经文要旨(非逐字译文):此一行道能令众生清净、超越愁悲、灭除苦忧、得正理、证涅槃,即四念处——比丘于身观身、于受观受、于心观心、于法观法,热忱、正知、正念,远离世间贪忧。
39
39
 
40
40
  **四念处之要**:
41
41
  - **身念处**:观身呼吸、行住坐卧、身体三十二分、四界(地水火风)、墓地九相
@@ -100,7 +100,7 @@
100
100
 
101
101
  ### 一切经(Sabba Sutta)
102
102
 
103
- 佛说:"何为一切?眼与色、耳与声、鼻与香、舌与味、身与触、意与法——此乃一切。除此之外若有人言别有'一切',唯口言尔,问之则不能答。"
103
+ 经文要旨(非逐字译文):一切,即眼与色、耳与声、鼻与香、舌与味、身与触、意与法。若有人说要舍此一切而另立一切,那只是言说:问之则不能答,更将陷于困恼——因为那已超出他所能了知的范围。
104
104
 
105
105
  **主旨**:将"世界"还原为六根六尘的当下经验,断除对超越实体(永恒灵魂、绝对实体)之执取。
106
106
 
@@ -138,7 +138,7 @@ GET https://fojin.app/api/search/semantic?q=<URL编码查询>&top_k=5 # 语义
138
138
  | 怎么发菩提心 | `references/teaching.md` §菩提心 | 七因果 + 自他相换 / 金洲传承 |
139
139
  | 暇满人身为何重要 | `references/teaching.md` §下士道 | 《道灯论·下士道章》(Toh 4465)|
140
140
  | 阿底峡为什么入藏 | `references/teaching.md` §传承与背景 | 智光王邀请 + 律仪整顿 |
141
- | 噶当派的核心是什么 | `references/teaching.md` §噶当派精神 | 《父法·子法》传承 |
141
+ | 噶当派的核心是什么 | `references/teaching.md` §依止善知识、§噶当六论 | 《父法·子法》传承 |
142
142
  | 怎么修拙火 / 灌顶 / 密法步骤 | — **拒答**:须具格上师亲传 | — |
143
143
 
144
144
  ## 教学路径(用于组织回答)
@@ -142,7 +142,7 @@ GET https://fojin.app/api/search/semantic?q=<URL编码查询>&top_k=5 # 语义
142
142
  | 什么是戒定慧三学 | `sources/visuddhimagga-excerpts.md` §戒定慧 | 《Vism》§I–XXIII 总纲 + AN 3.88 |
143
143
  | 四十种业处是什么 | `sources/visuddhimagga-excerpts.md` §业处 | 《Vism》§III–XI |
144
144
  | 什么是七清净十六观智 | `sources/visuddhimagga-excerpts.md` §七清净 | 《Vism》§XVIII–XXII + MN 24 |
145
- | 出入息念怎么修 | `references/teaching.md` §业处 §出入息念 | 《Vism》§VIII §145–245 + MN 118 |
145
+ | 出入息念怎么修 | `sources/visuddhimagga-excerpts.md` §出入息念 | 《Vism》§VIII §145–245 + MN 118 |
146
146
  | 缘起十二支怎么理解 | `references/teaching.md` §缘起 | 《Vism》§XVII + SN 12.2 |
147
147
  | 阿毗达摩心心所色怎么分 | `references/teaching.md` §阿毗达摩 | 《Vism》§XIV–XVII + Atthasālinī |
148
148
  | 教我具体的禅修步骤 | — 引导咨询具格禅师 | — |
@@ -36,9 +36,9 @@ verified_at: 2026-05-02
36
36
  - **标记法 / 腹部起伏主所缘**(Noting Method / rising falling)
37
37
  → 读 `sources/teachings-excerpts.md` §标记法 + `references/teaching.md` §标记法
38
38
  - **正念 / sati / 觉知不间断**
39
- → 读 `sources/teachings-excerpts.md` §正念力 + 巴利经引(MN 10)
39
+ → 读 `sources/teachings-excerpts.md` §正念之要在持续 + `references/teaching.md` §正念力 + 巴利经引(MN 10)
40
40
  - **十六观智 / 七清净 / 进度参照**
41
- → 读 `sources/teachings-excerpts.md` §十六观智 + `references/teaching.md` §观智次第
41
+ → 读 `sources/teachings-excerpts.md` §观智次第 + `references/teaching.md` §七清净与观智次第
42
42
  - **妄念多 / 散乱 / 怎么办**
43
43
  → 读 `sources/teachings-excerpts.md` §妄念多 + `references/teaching.md` §对治散乱
44
44
  - **刹那定 / 毗婆舍那禅那**(khaṇika-samādhi / vipassanā-jhāna)
@@ -115,9 +115,9 @@
115
115
 
116
116
  马哈希内观法以四念处经为根本经典依据。
117
117
 
118
- 经文核心:
118
+ 经文要旨(非逐字译文):
119
119
 
120
- > "诸比丘,此一行道,能令众生清净、超越愁悲、灭除苦忧、得正理、证涅槃,所谓四念处。何为四?比丘于身观身、于受观受、于心观心、于法观法,热忱(ātāpī)、正知(sampajāno)、正念(satimā),远离世间贪忧。"
120
+ 此一行道能令众生清净、超越愁悲、灭除苦忧、得正理、证涅槃,即四念处——比丘于身观身、于受观受、于心观心、于法观法,热忱(ātāpī)、正知(sampajāno)、正念(satimā),远离世间贪忧。
121
121
 
122
122
  **四念处全面观察**:
123
123
  - **身念处**:呼吸、行住坐卧、身体三十二分、四界、墓地九相
@@ -27,11 +27,11 @@ verified_at: 2026-05-02
27
27
 
28
28
  用户问题类型 →
29
29
  - **苦行 / 闭关 / 山中修行**(雪山闭关 / 荨麻为食 / 一座修法)
30
- → 读 `sources/namthar-excerpts.md` §雪山苦行 + `references/teaching.md` §出离与精进
30
+ → 读 `sources/namthar-excerpts.md` §雪山苦行 + `references/teaching.md` §出离心与精进闭关
31
31
  - **大手印 / 明空 / 本觉**(phyag chen / 心性 / rig pa)
32
- → 读 `sources/grubum-excerpts.md` §大手印道歌 + `references/teaching.md` §大手印见地
32
+ → 读 `sources/grubum-excerpts.md` §大手印见地 + `references/teaching.md` §大手印见地
33
33
  - **那洛六法 / 拙火 / 气脉明点**(tummo / 中阴 / 梦观)
34
- → 读 `sources/grubum-excerpts.md` §拙火与气脉 + `references/teaching.md` §那洛六法
34
+ → 读 `references/teaching.md` §那洛六法(只有名义与历史);具体修法 `sources/grubum-excerpts.md` §本目录不收录之内容(故意不收,按 HARD-GATE 统一回应)
35
35
  - **业果 / 忏悔 / 黑魔术过往**(早年咒杀仇家 / 玛尔巴的折磨 / 净罪)
36
36
  → 读 `sources/namthar-excerpts.md` §业与忏悔
37
37
  - **上师瑜伽 / 玛尔巴 / 信心**(依止善知识 / 译师玛尔巴 / 信心生起)
@@ -134,7 +134,7 @@ GET https://fojin.app/api/search/semantic?q=<URL编码查询>&top_k=5 # 语义
134
134
  | 玛尔巴为什么折磨米拉日巴 | `sources/namthar-excerpts.md` §业与忏悔 | 《尊者传》(BDRC: W1GS56158) |
135
135
  | 怎么生起出离心 | `sources/grubum-excerpts.md` §出离 | 《道歌集·无常之歌》 |
136
136
  | 怎么修拙火(具体方法)| — **拒答**:须具格上师传授 | — |
137
- | 觉受 (nyams) 与证悟的区别 | `references/teaching.md` §觉受 vs 证悟 | 《道歌集》多处 |
137
+ | 觉受 (nyams) 与证悟的区别 | `references/teaching.md` §觉受与证悟 | 《道歌集》多处 |
138
138
 
139
139
  ## 教学路径(用于组织回答)
140
140
 
@@ -46,7 +46,7 @@ verified_at: 2026-04-06
46
46
  - **天台教观**(教观纲宗 / 止观 / 一念心性)
47
47
  → 读 `references/teaching.md` §教宗天台 + `sources/jiaoguan-gangzong-excerpts.md`
48
48
  - **宗派融通**(性相融会 / 禅教律净 / 跨宗派)
49
- → 读 `references/teaching.md` §融通 + `sources/mituo-yaojie-excerpts.md` §现前一念
49
+ → 读 `references/teaching.md` §融通 + `sources/mituo-yaojie-excerpts.md` §一念心性
50
50
  - **修行方法**(持名念佛 / 持戒 / 占察忏)
51
51
  → 读 `references/teaching.md` §修行方法
52
52
  - **风格对话**("想和蕅益大师聊聊"/角色扮演请求)
@@ -43,7 +43,7 @@ verified_at: 2026-05-02
43
43
  - **应成中观正见 / 空性 / 辨了不了义**
44
44
  → 读 `sources/lamrim-excerpts.md` §毗钵舍那(vipaśyanā)章 + `references/teaching.md` §应成中观
45
45
  - **戒律 / 三聚戒 / 律仪根本**
46
- → 读 `references/teaching.md` §戒律根本
46
+ → 读 `references/teaching.md` §戒律为根本
47
47
  - **密宗 / 续部** → 仅介绍历史与原理,**不传授修法步骤**(参 HARD-GATE)
48
48
  - **格鲁派传承 / 甘丹寺 / 默朗钦摩 / 达赖班禅**
49
49
  → 读 `references/teaching.md` §传承与背景
@@ -152,8 +152,8 @@ GET https://fojin.app/api/search/semantic?q=<URL编码查询>&top_k=5 # 语义
152
152
  | 什么是三主要道 | `sources/lamrim-excerpts.md` §三主要道 | 《三主要道》(Lam gtso rnam gsum) |
153
153
  | 道次第怎么学 | `sources/lamrim-excerpts.md` §三士道 | 《菩提道次第广论》|
154
154
  | 应成中观和自续中观区别 | `references/teaching.md` §应成中观 | 《辨了不了义》+《入中论善显密意疏》|
155
- | 缘起性空怎么理解 | `references/teaching.md` §应成中观 §缘起性空 | 《辨了不了义》|
156
- | 戒定慧三学怎么学 | `references/teaching.md` §戒律根本 | 《广论》共下/中士道 |
155
+ | 缘起性空怎么理解 | `references/teaching.md` §应成中观正见 | 《辨了不了义》|
156
+ | 戒定慧三学怎么学 | `references/teaching.md` §戒律为根本 | 《广论》共下/中士道 |
157
157
  | 怎么修拙火 / 灌顶 / 生起圆满次第 | — **拒答**:须具格上师亲传 | — |
158
158
  | 格鲁派和宁玛派哪个好 | — **拒答**:不评判他派优劣 | — |
159
159
 
@@ -37,7 +37,7 @@ verified_at: 2026-04-06
37
37
 
38
38
  用户问题类型 →
39
39
  - **参禅方法**(话头 / 疑情 / 念佛是谁 / 禅七 / 行香坐禅)
40
- → 读 `sources/lengyanjing-excerpts.md` §心性 + `references/teaching.md` §参话头
40
+ → 读 `sources/lengyanjing-excerpts.md` §七处征心 + `references/teaching.md` §参话头
41
41
  - **禅宗教义**(明心见性 / 本来面目 / 开悟 / 桶底脱落)
42
42
  → 读 `references/teaching.md` §核心教导 + `sources/tanjing-excerpts.md`
43
43
  - **持戒与丛林**(戒律 / 丛林规矩 / 日常修行)
@@ -39,7 +39,7 @@ verified_at: 2026-04-06
39
39
  - **教义询问**(一念三千 / 三谛 / 五时八教 / 性具善恶)
40
40
  → 读 `references/teaching.md` + 引用 `sources/mohezhiguan-excerpts.md`
41
41
  - **修行方法**(止观 / 一心三观 / 四种三昧 / 六即判位)
42
- → 读 `sources/mohezhiguan-excerpts.md` §止觀法門 + 必要时 `references/teaching.md` §修行方法
42
+ → 读 `sources/mohezhiguan-excerpts.md` §圆顿止观开篇、§二十五方便、§一心三观、§六即佛 + 必要时 `references/teaching.md` §修行方法
43
43
  - **判教体系**(藏通别圆 / 化仪四教 / 五时分判)
44
44
  → 读 `references/teaching.md` §判教 + `sources/fahua-xuanyi-excerpts.md`
45
45
  - **法华经义理**(开权显实 / 会三归一 / 穷子喻)
@@ -138,7 +138,7 @@ GET https://fojin.app/api/search/semantic?q=<URL编码查询>&top_k=5 # 语义
138
138
  | 三谛圆融怎么理解 | `references/teaching.md` §三谛圆融 | 《法華玄義》卷二,T1716 |
139
139
  | 五时八教怎么分 | `references/teaching.md` §判教 | 《法華玄義》卷一,T1716 |
140
140
  | 止观怎么修 | `sources/mohezhiguan-excerpts.md` §二十五方便 | 《摩訶止觀》卷四,T1911 |
141
- | 一心三观 | `sources/mohezhiguan-excerpts.md` §一心三觀 | 《摩訶止觀》卷五,T1911 |
141
+ | 一心三观 | `sources/mohezhiguan-excerpts.md` §一心三观 | 《摩訶止觀》卷五,T1911 |
142
142
  | 四种三昧 | `references/teaching.md` §四种三昧 | 《摩訶止觀》卷二,T1911 |
143
143
  | 性具善恶 | `references/teaching.md` §性具 | 《觀音玄義》卷上,T1726 |
144
144
  | 入门从哪开始 | — | 《修習止觀坐禪法要》(小止觀),T1915 |
@@ -235,7 +235,7 @@ FAIL → 自动修复后重审。
235
235
  ## Step 5:写入文件细则
236
236
 
237
237
  ```bash
238
- python3 ${CLAUDE_SKILL_DIR}/tools/master_builder.py --spec generated-master.json --output masters/
238
+ python3 "${CLAUDE_SKILL_DIR}/tools/master_builder.py" --spec generated-master.json --output "${CLAUDE_SKILL_DIR}/masters/"
239
239
  ```
240
240
 
241
241
  `generated-master.json` 是审查通过后的生成规格,必含 `name`、`tradition`、`school`、`era`、
@@ -245,7 +245,7 @@ python3 ${CLAUDE_SKILL_DIR}/tools/master_builder.py --spec generated-master.json
245
245
  ### 生成后终验
246
246
 
247
247
  ```bash
248
- python3 ${CLAUDE_SKILL_DIR}/tools/verify_sources.py --final-check masters/master-{slug}/
248
+ python3 "${CLAUDE_SKILL_DIR}/tools/verify_sources.py" --final-check "${CLAUDE_SKILL_DIR}/masters/master-{slug}/"
249
249
  ```
250
250
 
251
251
  `--final-check` 离线验证 persona 目录包含 `SKILL.md`、`teaching.md`、`voice.md`、`meta.json`,
@@ -255,8 +255,8 @@ python3 ${CLAUDE_SKILL_DIR}/tools/verify_sources.py --final-check masters/master
255
255
  ### 生成目录结构
256
256
 
257
257
  ```
258
- masters/master-{slug}/
259
- ├── SKILL.md # /master-{slug} 触发(完整角色定义)
258
+ ${CLAUDE_SKILL_DIR}/masters/master-{slug}/
259
+ ├── SKILL.md # 注册后由 /master-{slug} 触发(完整角色定义)
260
260
  ├── teaching.md # 教义体系(可单独使用)
261
261
  ├── voice.md # 说法风格(可单独使用)
262
262
  └── meta.json # 元数据(版本、生成时间、数据来源)
@@ -265,9 +265,27 @@ masters/master-{slug}/
265
265
  ### 角色注册(按运行环境)
266
266
 
267
267
  **Claude Code 用户**
268
- 1. 生成的 SKILL.md 已放置在 `masters/master-{slug}/`
269
- 2. 确保 `masters/` 在 Claude Code skill 搜索路径中(检查 `.claude/settings.json` 的 `skillDirs` 配置)
270
- 3. 完成后自动可通过 `/master-{slug}` 触发
268
+
269
+ 终验通过后注册:
270
+
271
+ ```bash
272
+ python3 "${CLAUDE_SKILL_DIR}/tools/master_builder.py" --register "${CLAUDE_SKILL_DIR}/masters/master-{slug}"
273
+ ```
274
+
275
+ 它在 `~/.claude/skills/master-{slug}` 建一个指向生成目录的链接(Windows 上建 junction),输出 JSON:
276
+
277
+ - `invoke`:调用命令,照此告知用户;
278
+ - `restart_required: true`:`~/.claude/skills/` 是这次才建的,需重启 Claude Code 才会被发现;否则当前会话内即可调用;
279
+ - 退出码 1 且提示 `not replacing`:同名 skill 已存在(例如重新生成了一位预置祖师)。**不要删除或覆盖它**,把情况告诉用户,由用户决定改名或自行移除。
280
+
281
+ 为什么需要这一步:Claude Code 只加载 `<skills 目录>/<名字>/SKILL.md`,不往下扫,也没有 `skillDirs` 之类的设置。
282
+ 2026-09-16 用 Claude Code 2.1.273 在隔离配置中实测:放在 `~/.claude/skills/create-master/masters/` 下的 persona
283
+ 不出现在 `/skills` 里;注册出链接后,同一会话内即出现。生成目录留在 `masters/` 是为了让
284
+ `master-skill update` 更新运行时的时候不丢失用户生成的 persona。
285
+
286
+ 以插件方式安装时,`${CLAUDE_SKILL_DIR}` 是按版本号区分的插件缓存目录:`claude plugin update` 之后
287
+ 它换到新版本目录,新目录下的 `masters/` 是空的(实测 0.12.11 → 0.12.12)。要长期保留自己生成的 persona,
288
+ 请用 `npx master-skill install create-master` 或 git clone 方式安装生成器。
271
289
 
272
290
  **OpenClaw 用户**
273
291
  1. 将 `masters/master-{slug}/` 复制到 OpenClaw 的 skills 目录
@@ -278,8 +296,8 @@ masters/master-{slug}/
278
296
 
279
297
  ```
280
298
  已生成「{master_name}」教学角色
281
- 目录:masters/master-{slug}/
282
- 调用命令:/master-{slug}
299
+ 目录:${CLAUDE_SKILL_DIR}/masters/master-{slug}/
300
+ 调用命令:/master-{slug}(已注册到 ~/.claude/skills/master-{slug})
283
301
  包含文件:SKILL.md, teaching.md, voice.md, meta.json
284
302
  数据来源:{n} 条经文,{m} 个知识图谱实体
285
303
  ```
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """Gate: a "read `file.md` §section" instruction must land on something.
3
+
4
+ Every persona's SKILL.md routes a question to material: a decision tree says
5
+ 读 `references/teaching.md` §参话头, a Quick Reference row says the same. The
6
+ model follows those pointers literally. Nothing checked that the section they
7
+ name exists, and the files they point into have been rewritten many times since
8
+ the routing was written — sections renamed to a book's real chapter titles,
9
+ material moved from references/ to sources/.
10
+
11
+ 2026-09-16, measured across the repo: 18 of 198 section pointers in nine personas
12
+ named a section the target file does not have, and so did one in the generator's
13
+ own SKILL.md. Most were renames (§戒律根本 for 戒律为根本), but not all were
14
+ harmless:
15
+
16
+ - master-milarepa routed 那洛六法 / 拙火 / 气脉明点 to
17
+ `sources/grubum-excerpts.md` §拙火与气脉. That file has no such section on
18
+ purpose — it lists tummo among what it deliberately does not hold, with the
19
+ answer to give instead. The pointer sent the model to look for exactly the
20
+ material the persona is forbidden to supply.
21
+ - master-mahasi-sayadaw routed 十六观智 to §十六观智 in an excerpt file whose
22
+ section is 观智次第 and which says the book numbers seventeen, not sixteen.
23
+ - master-ajahn-chah sent 心的训练 to references/teaching.md; the section is in
24
+ sources/teachings-excerpts.md.
25
+
26
+ A section counts as present when the name appears, ignoring punctuation and
27
+ spacing, in a heading of the target file or in a **bold label** — the persona
28
+ docs use bold labels (**十念法**, **数息观**) as sub-section markers, and a model
29
+ searching for the name finds either. Parenthesised glosses are optional on both
30
+ sides of the match, so §觉受与证悟 finds `6. 觉受 (nyams) 与证悟 (rtogs pa) 的区分`.
31
+ Prose may run on after a name (§版权分级 Tier B/C 流程), so trailing words split
32
+ off by spaces are dropped one at a time; the first word must still be found.
33
+
34
+ A path with a directory in it must name a file that exists even with no section.
35
+ A bare `teaching.md` without one is prose — the generator docs name the files
36
+ they produce that way — and is not a pointer.
37
+ """
38
+ from __future__ import annotations
39
+
40
+ import re
41
+ import sys
42
+ from pathlib import Path
43
+
44
+ ROOT = Path(__file__).resolve().parent.parent
45
+ PREBUILT_DIR = ROOT / "prebuilt"
46
+
47
+ # The docs the model is told to follow: every persona and mode, and the
48
+ # generator skill with its references.
49
+ DOC_GLOBS = ("prebuilt/**/*.md", "SKILL.md", "references/*.md")
50
+
51
+ _PATH = re.compile(r"`((?:[\w-]+/)*[\w.-]+\.md)`")
52
+ # A section name ends at the next separator a routing line uses: `+`, a table
53
+ # pipe, CJK punctuation, a backtick or an opening gloss.
54
+ _SECTIONS = re.compile(r"(?:[ \t、]*§[^§`++\n,、;;|。()()]*)+")
55
+ _SECTION = re.compile(r"§([^§、]*)")
56
+ _BOLD = re.compile(r"\*\*([^*\n]+)\*\*")
57
+ _GLOSS = re.compile(r"[((][^()()]*[))]")
58
+ _NUMBERING = re.compile(r"^\s*(?:\d+\.|[①-⑳])\s*")
59
+ _NOISE = re.compile(r"[\s·、,,。.::—\-–《》「」\"'`*§⚠️]")
60
+
61
+
62
+ def _norm(text: str) -> str:
63
+ return _NOISE.sub("", _NUMBERING.sub("", text)).lower()
64
+
65
+
66
+ def anchor_forms(anchor: str) -> set[str]:
67
+ """一个标题或粗体标签可被匹配的写法:连同括注,或去掉括注。"""
68
+ return {_norm(re.sub(r"[()()]", "", anchor)), _norm(_GLOSS.sub("", anchor))}
69
+
70
+
71
+ def anchors(lines: list[str]) -> list[str]:
72
+ found = [line.lstrip("#").strip() for line in lines if line.startswith("#")]
73
+ for line in lines:
74
+ found.extend(_BOLD.findall(line))
75
+ return found
76
+
77
+
78
+ def section_present(name: str, lines: list[str]) -> bool:
79
+ forms = {form for anchor in anchors(lines) for form in anchor_forms(anchor)}
80
+ words = name.split()
81
+ while words:
82
+ wanted = _norm(" ".join(words))
83
+ if wanted and any(wanted in form for form in forms):
84
+ return True
85
+ words.pop()
86
+ return not _norm(name)
87
+
88
+
89
+ def references(line: str) -> list[tuple[str, list[str]]]:
90
+ """[(路径, [小节名…])]:这一行里的每个文件指针及其所指小节。"""
91
+ out = []
92
+ for m in _PATH.finditer(line):
93
+ tail = _SECTIONS.match(line, m.end())
94
+ names = []
95
+ if tail:
96
+ names = [n.strip() for n in _SECTION.findall(tail.group(0)) if n.strip()]
97
+ out.append((m.group(1), names))
98
+ return out
99
+
100
+
101
+ def _skill_root(doc: Path) -> Path:
102
+ rel = doc.relative_to(ROOT)
103
+ return ROOT / rel.parts[0] / rel.parts[1] if rel.parts[0] == "prebuilt" else ROOT
104
+
105
+
106
+ def resolve(doc: Path, rel: str) -> Path | None:
107
+ for base in (_skill_root(doc), doc.parent, ROOT):
108
+ if (base / rel).is_file():
109
+ return base / rel
110
+ return None
111
+
112
+
113
+ def dangling(docs: list[Path]) -> tuple[int, list[str]]:
114
+ """(检查过的指针数, [问题…])。"""
115
+ examined, problems = 0, []
116
+ cache: dict[Path, list[str]] = {}
117
+ for doc in docs:
118
+ for number, line in enumerate(doc.read_text(encoding="utf-8").splitlines(), 1):
119
+ if "{" in line:
120
+ continue # 模板行,如 `prebuilt/{slug}/…` 下的 `references/teaching.md`
121
+ where = f"{doc.relative_to(ROOT)}:{number}"
122
+ for rel, names in references(line):
123
+ if "/" not in rel and not names:
124
+ continue # 裸文件名是行文,不是指针
125
+ examined += 1
126
+ target = resolve(doc, rel)
127
+ if target is None:
128
+ problems.append(f"{where} `{rel}` does not exist")
129
+ continue
130
+ if target not in cache:
131
+ cache[target] = target.read_text(encoding="utf-8").splitlines()
132
+ for name in names:
133
+ if not section_present(name, cache[target]):
134
+ problems.append(f"{where} `{rel}` has no section §{name}")
135
+ return examined, problems
136
+
137
+
138
+ def model_facing_docs() -> list[Path]:
139
+ return sorted({p for pattern in DOC_GLOBS for p in ROOT.glob(pattern) if p.is_file()})
140
+
141
+
142
+ def main() -> int:
143
+ examined, problems = dangling(model_facing_docs())
144
+ if examined == 0:
145
+ print("FAIL: found no file pointers at all — this gate examined an empty set.")
146
+ return 1
147
+ if problems:
148
+ print(f"FAIL: {len(problems)} of {examined} file pointer(s) do not land:")
149
+ for problem in problems:
150
+ print(f" {problem}")
151
+ print(
152
+ "\nThe model follows these pointers literally. A section that is not there\n"
153
+ "leaves it to improvise the material it was sent to read. Point at a heading\n"
154
+ "or **bold label** the file actually has, or drop the § and name the file."
155
+ )
156
+ return 1
157
+ print(f"OK: all {examined} file pointers land on a file and section that exist.")
158
+ return 0
159
+
160
+
161
+ if __name__ == "__main__":
162
+ sys.exit(main())
@@ -309,6 +309,76 @@ def build_from_spec(spec: dict, output_dir: str) -> dict:
309
309
  }
310
310
 
311
311
 
312
+ _SKILL_NAME = re.compile(r"^name:[ \t]*(\S+)[ \t]*$", re.MULTILINE)
313
+
314
+
315
+ def register_teacher(teacher_dir: str, skills_dir: str) -> dict:
316
+ """Make a generated persona invocable by linking it into a skills directory.
317
+
318
+ The generator writes to `${CLAUDE_SKILL_DIR}/masters/master-{slug}/`, so that
319
+ `master-skill update` can carry generated personas across runtime updates.
320
+ Claude Code does not look there: it loads `<skills dir>/<name>/SKILL.md` and
321
+ scans no deeper. Measured 2026-09-16 with Claude Code 2.1.273 in an isolated
322
+ config, `/skills` listed a probe skill at `~/.claude/skills/master-control/`
323
+ and not one at `~/.claude/skills/create-master/masters/master-probe/`; a
324
+ directory symlink `~/.claude/skills/master-probe` made it appear in the same
325
+ session, without a restart.
326
+
327
+ An existing entry of the same name is never replaced: a user regenerating a
328
+ prebuilt master (`master-huineng`) must not overwrite the installed one.
329
+ """
330
+ teacher = Path(teacher_dir).resolve()
331
+ skill_md = teacher / "SKILL.md"
332
+ if not skill_md.is_file():
333
+ raise ValueError(f"{teacher} has no SKILL.md")
334
+ text = skill_md.read_text(encoding="utf-8")
335
+ frontmatter = text.split("---", 2)[1] if text.startswith("---") else ""
336
+ match = _SKILL_NAME.search(frontmatter)
337
+ if not match or match.group(1) != teacher.name:
338
+ raise ValueError(
339
+ f"SKILL.md name must equal the directory name {teacher.name!r} — "
340
+ "Claude Code invokes the skill by that name"
341
+ )
342
+
343
+ skills = Path(skills_dir).expanduser()
344
+ created_skills_dir = not skills.is_dir()
345
+ skills.mkdir(parents=True, exist_ok=True)
346
+ link = skills / teacher.name
347
+
348
+ if link.exists() or link.is_symlink():
349
+ if link.exists() and link.resolve() == teacher:
350
+ return {
351
+ "registered": str(link),
352
+ "target": str(teacher),
353
+ "invoke": f"/{teacher.name}",
354
+ "already_registered": True,
355
+ "restart_required": False,
356
+ }
357
+ raise ValueError(
358
+ f"{link} already exists and is not this persona; not replacing it. "
359
+ f"Rename the generated persona, or remove {link} yourself."
360
+ )
361
+
362
+ try:
363
+ os.symlink(teacher, link, target_is_directory=True)
364
+ except OSError:
365
+ if os.name != "nt":
366
+ raise
367
+ import _winapi # symlinks need a privilege on Windows; junctions do not
368
+
369
+ _winapi.CreateJunction(str(teacher), str(link))
370
+
371
+ return {
372
+ "registered": str(link),
373
+ "target": str(teacher),
374
+ "invoke": f"/{teacher.name}",
375
+ "already_registered": False,
376
+ # Claude Code watches skill directories that existed when the session
377
+ # started; a skills directory created now is seen after a restart.
378
+ "restart_required": created_skills_dir,
379
+ }
380
+
381
+
312
382
  def main(argv: list[str] | None = None) -> int:
313
383
  parser = argparse.ArgumentParser(
314
384
  description="Build a create-master persona from an explicit generation spec"
@@ -320,15 +390,29 @@ def main(argv: list[str] | None = None) -> int:
320
390
  action="store_true",
321
391
  help="run a deterministic no-network generation smoke",
322
392
  )
323
- parser.add_argument("--output", required=True, help="master output directory")
393
+ modes.add_argument(
394
+ "--register",
395
+ metavar="TEACHER_DIR",
396
+ help="link a generated persona into --skills-dir so it can be invoked",
397
+ )
398
+ parser.add_argument("--output", help="master output directory (with --spec / --offline-smoke)")
399
+ parser.add_argument(
400
+ "--skills-dir",
401
+ default=os.path.join("~", ".claude", "skills"),
402
+ help="skills directory to register into (with --register; default ~/.claude/skills)",
403
+ )
324
404
  args = parser.parse_args(argv)
405
+ if not args.register and not args.output:
406
+ parser.error("--output is required with --spec / --offline-smoke")
325
407
 
326
408
  try:
327
- if args.offline_smoke:
328
- spec = _offline_smoke_spec()
409
+ if args.register:
410
+ summary = register_teacher(args.register, args.skills_dir)
411
+ elif args.offline_smoke:
412
+ summary = build_from_spec(_offline_smoke_spec(), args.output)
329
413
  else:
330
414
  spec = json.loads(Path(args.spec).read_text(encoding="utf-8"))
331
- summary = build_from_spec(spec, args.output)
415
+ summary = build_from_spec(spec, args.output)
332
416
  except (OSError, json.JSONDecodeError, ValueError) as exc:
333
417
  print(f"ERROR: {exc}", file=sys.stderr)
334
418
  return 1