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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +5 -1
- package/.codex/INSTALL.md +23 -16
- package/.cursor-plugin/plugin.json +1 -1
- package/.opencode/INSTALL.md +29 -16
- package/README.md +2 -2
- package/README_EN.md +2 -2
- package/SKILL.md +2 -2
- package/bin/cli.mjs +96 -0
- package/gemini-extension.json +1 -1
- package/hooks/session_start.py +20 -7
- package/package.json +2 -2
- package/prebuilt/master-ajahn-chah/SKILL.md +4 -4
- package/prebuilt/master-ajahn-chah/sources/sutta-excerpts.md +3 -3
- package/prebuilt/master-atisha/SKILL.md +1 -1
- package/prebuilt/master-buddhaghosa/SKILL.md +1 -1
- package/prebuilt/master-mahasi-sayadaw/SKILL.md +2 -2
- package/prebuilt/master-mahasi-sayadaw/sources/teachings-excerpts.md +2 -2
- package/prebuilt/master-milarepa/SKILL.md +4 -4
- package/prebuilt/master-ouyi/SKILL.md +1 -1
- package/prebuilt/master-tsongkhapa/SKILL.md +3 -3
- package/prebuilt/master-xuyun/SKILL.md +1 -1
- package/prebuilt/master-zhiyi/SKILL.md +2 -2
- package/references/workflow-details.md +27 -9
- package/scripts/validate-section-references.py +162 -0
- package/tools/master_builder.py +88 -4
|
@@ -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.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.
|
|
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
|
-
|
|
10
|
-
ln -
|
|
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
|
|
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
|
-
-
|
|
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.
|
|
5
|
+
"version": "0.12.12",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "xr843",
|
|
8
8
|
"email": "xr843@users.noreply.github.com"
|
package/.opencode/INSTALL.md
CHANGED
|
@@ -2,36 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## Quick Install
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
"
|
|
19
|
+
"skills": { "paths": ["/absolute/path/to/Master-skill/prebuilt"] }
|
|
10
20
|
}
|
|
11
21
|
```
|
|
12
22
|
|
|
13
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
- **
|
|
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
|
-
|
|
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(),
|
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.12",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/hooks/session_start.py
CHANGED
|
@@ -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,
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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.
|
|
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`
|
|
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
|
-
|
|
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
|
-
| 出入息念怎么修 | `
|
|
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`
|
|
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
|
-
|
|
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`
|
|
32
|
+
→ 读 `sources/grubum-excerpts.md` §大手印见地 + `references/teaching.md` §大手印见地
|
|
33
33
|
- **那洛六法 / 拙火 / 气脉明点**(tummo / 中阴 / 梦观)
|
|
34
|
-
→ 读 `
|
|
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`
|
|
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`
|
|
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`
|
|
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`
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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())
|
package/tools/master_builder.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
328
|
-
|
|
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
|
-
|
|
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
|