master-skill 0.12.13 → 0.12.14

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.13",
12
+ "version": "0.12.14",
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.13",
4
+ "version": "0.12.14",
5
5
  "author": {
6
6
  "name": "xr843",
7
7
  "email": "xr843@users.noreply.github.com"
@@ -2,7 +2,7 @@
2
2
  "name": "master-skill",
3
3
  "displayName": "Master Skill",
4
4
  "description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传.",
5
- "version": "0.12.13",
5
+ "version": "0.12.14",
6
6
  "author": {
7
7
  "name": "xr843",
8
8
  "email": "xr843@users.noreply.github.com"
package/SKILL.md CHANGED
@@ -5,13 +5,11 @@ argument-hint: <法师名称>
5
5
  version: 1.0.0
6
6
  user-invocable: true
7
7
  allowed-tools:
8
- - Bash
9
8
  - Read
10
- - Write
11
- - Edit
12
9
  - Glob
13
10
  - Grep
14
- - WebFetch
11
+ - Bash(python3 ${CLAUDE_SKILL_DIR}/tools/*)
12
+ - Bash(python3 "${CLAUDE_SKILL_DIR}/tools/*)
15
13
  ---
16
14
 
17
15
  # Master-skill — 佛教法师教学角色生成器
@@ -64,6 +62,10 @@ allowed-tools:
64
62
 
65
63
  ## 主流程(生成新法师)
66
64
 
65
+ ### Step 0:检查运行环境
66
+
67
+ 先运行 `python3 "${CLAUDE_SKILL_DIR}/tools/check_deps.py"`。生成器的每个工具启动时都要导入 `requests`、`pyyaml`、`pypinyin`,缺任何一个都会直接报 `ModuleNotFoundError`,连离线步骤也跑不了。退出码非 0 时停下,把它打印的安装方法原样告诉用户(系统 Python 拒绝 pip 时改用虚拟环境);**不要擅自安装**,那会改动用户的环境。
68
+
67
69
  ### Step 1:信息录入
68
70
 
69
71
  加载 `${CLAUDE_SKILL_DIR}/prompts/intake.md`,3 问模式收集:①法师名称(FoJin KG 自动匹配) ②关注方面(教义/修行/讲解/全部) ③语言偏好(按传承默认)。
package/bin/cli.mjs CHANGED
@@ -4,6 +4,7 @@ import fs from "fs";
4
4
  import path from "path";
5
5
  import os from "os";
6
6
  import { fileURLToPath } from "url";
7
+ import { spawnSync } from "child_process";
7
8
 
8
9
  // fileURLToPath (not new URL().pathname) — on Windows the URL pathname is
9
10
  // "/C:/…", which fs cannot resolve, so every command saw an empty prebuilt/.
@@ -151,6 +152,14 @@ function cpR(src, dest) {
151
152
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
152
153
  const s = path.join(src, entry.name);
153
154
  const d = path.join(dest, entry.name);
155
+ // The package ships no links. One in the source is left over from running
156
+ // the clone instructions' `ln -sf` twice, which — with the target already a
157
+ // link to a directory — creates prebuilt/<skill>/<skill> pointing at its own
158
+ // parent. copyFileSync on it threw EISDIR and the install crashed.
159
+ if (entry.isSymbolicLink()) {
160
+ console.error(` (skipped link ${s} — not part of the package)`);
161
+ continue;
162
+ }
154
163
  if (entry.isDirectory()) cpR(s, d);
155
164
  else fs.copyFileSync(s, d);
156
165
  }
@@ -365,6 +374,35 @@ function resolveMasterDir(input) {
365
374
 
366
375
  // Returns the number of failures so main can set a non-zero exit code —
367
376
  // scripted callers must be able to tell a typo from a clean install.
377
+ // create-master's tools import requests, pyyaml and pypinyin at startup and exit
378
+ // with ModuleNotFoundError without them. tools/check_deps.py (standard library
379
+ // only) reports which are missing and how to install them.
380
+ function pythonBin() {
381
+ return process.env.PYTHON || (process.platform === "win32" ? "python" : "python3");
382
+ }
383
+
384
+ function generatorDependencies(generatorDir) {
385
+ const script = path.join(generatorDir, "tools", "check_deps.py");
386
+ if (!fs.existsSync(script)) return null;
387
+ const result = spawnSync(pythonBin(), [script, "--json"], { encoding: "utf8", timeout: 30000 });
388
+ if (result.error) {
389
+ return { ok: false, pythonMissing: true, message: `${pythonBin()} was not found; create-master needs Python 3.9+` };
390
+ }
391
+ try {
392
+ const report = JSON.parse(result.stdout);
393
+ if (report.ok) return { ok: true };
394
+ const what = report.python_too_old
395
+ ? `Python ${report.python} is older than 3.9`
396
+ : `missing Python package(s) ${report.missing.join(", ")}`;
397
+ return {
398
+ ok: false,
399
+ message: `create-master: ${what} — run: ${pythonBin()} "${script}" for install steps`,
400
+ };
401
+ } catch {
402
+ return { ok: false, message: `create-master: could not check Python dependencies (${(result.stderr || "").trim()})` };
403
+ }
404
+ }
405
+
368
406
  function cmdInstall(names) {
369
407
  fs.mkdirSync(SKILLS_DIR, { recursive: true });
370
408
  let failed = 0;
@@ -382,8 +420,10 @@ function cmdInstall(names) {
382
420
  }
383
421
  const src = path.join(PACKAGE_ROOT, skill.source);
384
422
  const dest = path.join(SKILLS_DIR, skill.install_dir);
423
+ let deps = null;
385
424
  if (skill.kind === "generator") {
386
425
  replaceGeneratorInstall(skill, src, dest);
426
+ deps = generatorDependencies(dest);
387
427
  } else {
388
428
  // Clear any previous install first: files renamed or removed upstream
389
429
  // must not linger as stale skill content under ~/.claude/skills/.
@@ -391,6 +431,7 @@ function cmdInstall(names) {
391
431
  cpR(src, dest);
392
432
  }
393
433
  console.log(` ✓ ${name} → ${dest}`);
434
+ if (deps && !deps.ok) console.log(` ! ${deps.message}`);
394
435
  }
395
436
  return failed;
396
437
  }
@@ -503,6 +544,10 @@ function expectedInstallFiles(skill) {
503
544
  const walk = (rel) => {
504
545
  const abs = path.join(src, rel);
505
546
  if (!fs.existsSync(abs)) return;
547
+ // Same rule as cpR: install skips links, so they are not expected files.
548
+ // Following a self-referencing one walked the loop to the OS link limit and
549
+ // reported hundreds of phantom missing files.
550
+ if (fs.lstatSync(abs).isSymbolicLink()) return;
506
551
  if (fs.statSync(abs).isDirectory()) {
507
552
  if (path.basename(abs) === "__pycache__") return;
508
553
  for (const entry of fs.readdirSync(abs)) {
@@ -565,6 +610,15 @@ function installedProblems() {
565
610
  }
566
611
  }
567
612
 
613
+ const generator = CATALOG.skills.find((skill) => skill.kind === "generator");
614
+ const generatorDir = generator && path.join(SKILLS_DIR, generator.install_dir);
615
+ if (generatorDir && fs.existsSync(path.join(generatorDir, "SKILL.md"))) {
616
+ const deps = generatorDependencies(generatorDir);
617
+ if (deps && !deps.ok) {
618
+ problems.push({ code: "generator-dependencies", name: generator.name, message: deps.message });
619
+ }
620
+ }
621
+
568
622
  // Personas registered by create-master are links into create-master/masters/;
569
623
  // deleting or moving a generated persona by hand leaves its link pointing at
570
624
  // nothing. (`uninstall create-master` refuses while personas exist, and with
@@ -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.13",
4
+ "version": "0.12.14",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "master-skill",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
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": {
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env python3
2
+ """Check the Python packages create-master's tools import at startup.
3
+
4
+ Every generator tool imports `fojin_bridge` (which imports `requests`) or
5
+ `skill_writer` (which imports `yaml` and `pypinyin`) at module level. Without those
6
+ packages each one exits with `ModuleNotFoundError` before doing anything — even
7
+ `master_builder.py --offline-smoke`, which never touches the network. Measured
8
+ 2026-09-17 in a clean venv: `rag_query.py`, `sutra_collector.py` and
9
+ `master_builder.py` all failed that way.
10
+
11
+ Nothing told an npx user to install them. The only mention was the clone guide's
12
+ `pip install -r requirements.txt`, and on recent Debian, Ubuntu and Homebrew Pythons
13
+ that command is itself refused as an externally-managed environment (PEP 668).
14
+
15
+ Standard library only, so it runs where the tools cannot.
16
+
17
+ Usage:
18
+ python3 tools/check_deps.py # exit 1 and print how to install if missing
19
+ python3 tools/check_deps.py --json
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import importlib.util
25
+ import json
26
+ import platform
27
+ import sys
28
+ from pathlib import Path
29
+
30
+ # import name -> distribution name in requirements.txt
31
+ REQUIRED = {"requests": "requests", "yaml": "pyyaml", "pypinyin": "pypinyin"}
32
+ MIN_PYTHON = (3, 9)
33
+ REQUIREMENTS = Path(__file__).resolve().parent.parent / "requirements.txt"
34
+
35
+
36
+ def missing(find_spec=importlib.util.find_spec) -> list[str]:
37
+ """Distributions whose import name cannot be found."""
38
+ return [dist for module, dist in REQUIRED.items() if find_spec(module) is None]
39
+
40
+
41
+ def guidance(missing_dists: list[str], requirements: Path = REQUIREMENTS) -> str:
42
+ venv_python = (
43
+ r"%USERPROFILE%\.venvs\master-skill\Scripts\python"
44
+ if sys.platform == "win32"
45
+ else "~/.venvs/master-skill/bin/python"
46
+ )
47
+ return "\n".join(
48
+ [
49
+ "create-master needs Python packages that are not installed: "
50
+ + ", ".join(missing_dists),
51
+ "",
52
+ f' python3 -m pip install -r "{requirements}"',
53
+ "",
54
+ 'If pip refuses with "externally-managed-environment" (PEP 668), use a virtual environment:',
55
+ "",
56
+ " python3 -m venv ~/.venvs/master-skill",
57
+ f' {venv_python} -m pip install -r "{requirements}"',
58
+ "",
59
+ "and start Claude Code with that environment active, so that `python3` is its interpreter.",
60
+ ]
61
+ )
62
+
63
+
64
+ def main(argv: list[str] | None = None) -> int:
65
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
66
+ parser.add_argument("--json", action="store_true", help="print a JSON result")
67
+ args = parser.parse_args(argv)
68
+
69
+ too_old = sys.version_info < MIN_PYTHON
70
+ absent = missing()
71
+ ok = not absent and not too_old
72
+ if args.json:
73
+ print(
74
+ json.dumps(
75
+ {
76
+ "ok": ok,
77
+ "python": platform.python_version(),
78
+ "python_too_old": too_old,
79
+ "missing": absent,
80
+ "requirements": str(REQUIREMENTS),
81
+ }
82
+ )
83
+ )
84
+ elif ok:
85
+ print(f"OK: Python {platform.python_version()} with {', '.join(REQUIRED.values())}")
86
+ else:
87
+ if too_old:
88
+ print(f"create-master needs Python {'.'.join(map(str, MIN_PYTHON))}+; this is {platform.python_version()}.")
89
+ if absent:
90
+ print(guidance(absent))
91
+ return 0 if ok else 1
92
+
93
+
94
+ if __name__ == "__main__":
95
+ sys.exit(main())