Files
xianren 3f81cbb955 feat(v0.6): namespace all 14 master slash commands under /master-<slug> prefix
Slash command discoverability cleanup. Claude Code users typically have
50+ skills installed; bare-word commands like /atisha and /zhiyi got
scattered across the /-completion list. v0.6 prefixes all 14 master
slash commands with master- so they cluster under /m<tab> and clearly
signal "this is a Master-skill master skill".

Scope of rename
---------------
* Slash commands: /zhiyi → /master-zhiyi, /huineng → /master-huineng,
  ... all 14 affected.
* Directory layout: prebuilt/<slug>/ → prebuilt/master-<slug>/ for all
  14 masters (git mv preserves history).
* Frontmatter: each prebuilt/master-<slug>/SKILL.md updates `name:` to
  master-<slug>.
* compare-masters and create-master meta-skills are intentionally
  unchanged — they're already prefixed by their nature, and
  /master-compare-masters would be doublespeak.

Decoupling: fojin.app/chat is NOT affected
------------------------------------------
The fojin web frontend's master dropdown uses bare slug IDs (atisha,
huineng, ...) and is already grouped under "法师模式" in its UI. Backend
master_profiles.py keeps `id="atisha"` etc. unchanged. No fojin-side
migration required. The two surfaces (Claude Code slash + fojin
dropdown) are now formally decoupled by design, not coincidence.

Compatibility
-------------
* NPX installer accepts both forms: `npx master-skill install zhiyi`
  (short) and `install master-zhiyi` (full) both resolve to the same
  prebuilt/master-zhiyi/ source. Install destination is always
  ~/.claude/skills/master-<slug>/. Backward-compatible uninstall
  handles legacy non-prefixed installs (~/.claude/skills/zhiyi/).
* The cli.mjs already used `master-${name}` for install destinations
  (since v0.3 NPX installer was added), so existing v0.4/v0.5 NPX
  users were already getting the prefix in skills/ — only the source
  prebuilt/ layout and slash commands change in v0.6.

Files updated
-------------
* 14 directories renamed (28 files moved, 0 content changes).
* 14 SKILL.md frontmatter `name:` fields.
* prebuilt/compare/SKILL.md: 43 slug references updated to prefixed form.
* bin/cli.mjs: resolveMasterDir helper accepts both short and full;
  cmdInstall and cmdUninstall handle legacy paths.
* .github/workflows/validate-and-test.yml: fidelity-smoke MASTERS
  rotation array updated to all 14 prefixed names (was 8 hardcoded
  汉传 only — now properly rotates across the full set).
* scripts/{validate,cite,query,test-fidelity}.py: --master arg help
  text examples.
* README.md + README_EN.md: situational guidance table, install
  snippets, master cards. New v0.6 release banner.
* SKILL.md (project-level) preset list with new slash names.
* ETHICS.md Tier table slug references (4).
* All plugin manifests bumped 0.5.0 → 0.6.0 with description noting
  the /master-<slug> invocation pattern.
* CHANGELOG.md: [0.6.0] section with breaking-change notice and
  migration commands for existing NPX users.

Validation
----------
* python scripts/validate.py --strict           ✓ 15 masters pass
* python scripts/validate-fidelity.py           ✓ all valid
* python scripts/test-fidelity.py --all --dry-run  ✓
* pytest tests/                                  ✓ 31 passed, 6 skipped
* node bin/cli.mjs list                          ✓ shows all 14 with
                                                   master- prefix
* node bin/cli.mjs install zhiyi                 ✓ resolves to
                                                   prebuilt/master-zhiyi/
* node bin/cli.mjs install master-zhiyi          ✓ resolves to same

Migration for existing v0.4/v0.5 users
--------------------------------------
    npx master-skill@0.5 uninstall zhiyi huineng xuanzang ...
    # OR: rm -rf ~/.claude/skills/{zhiyi,huineng,...}
    npx master-skill@latest install --all

Then start a new Claude Code session; the new slash commands are
/master-zhiyi, /master-huineng, etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:43:36 +08:00

170 lines
5.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
import fs from "fs";
import path from "path";
import os from "os";
const PREBUILT = path.join(
path.dirname(new URL(import.meta.url).pathname),
"..",
"prebuilt"
);
const SKILLS_DIR = path.join(os.homedir(), ".claude", "skills");
// --- helpers ---
function cpR(src, dest) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const s = path.join(src, entry.name);
const d = path.join(dest, entry.name);
if (entry.isDirectory()) cpR(s, d);
else fs.copyFileSync(s, d);
}
}
function parseFrontmatter(filepath) {
const text = fs.readFileSync(filepath, "utf8");
const m = text.match(/^---\n([\s\S]*?)\n---/);
if (!m) return {};
const fm = {};
for (const line of m[1].split("\n")) {
const idx = line.indexOf(":");
if (idx > 0 && !line.startsWith(" ") && !line.startsWith("-")) {
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
}
}
return fm;
}
function availableMasters() {
if (!fs.existsSync(PREBUILT)) return [];
return fs
.readdirSync(PREBUILT, { withFileTypes: true })
.filter((d) => d.isDirectory() && d.name !== "compare")
.map((d) => {
const skillMd = path.join(PREBUILT, d.name, "SKILL.md");
const fm = fs.existsSync(skillMd) ? parseFrontmatter(skillMd) : {};
return { name: d.name, description: fm.description || "" };
});
}
// --- commands ---
function cmdList() {
const masters = availableMasters();
if (!masters.length) {
console.log("No prebuilt masters found.");
return;
}
console.log(`\nAvailable masters (${masters.length}):\n`);
const nameW = Math.max(...masters.map((m) => m.name.length), 4);
for (const m of masters) {
const desc = m.description.length > 80 ? m.description.slice(0, 77) + "..." : m.description;
console.log(` ${m.name.padEnd(nameW)} ${desc}`);
}
console.log();
}
// Resolve a master directory accepting both short ("zhiyi") and full
// ("master-zhiyi") forms. Returns the absolute prebuilt/<dir> path or null.
function resolveMasterDir(input) {
const candidates = [
path.join(PREBUILT, input), // exact: master-zhiyi
path.join(PREBUILT, `master-${input}`), // short: zhiyi → master-zhiyi
];
return candidates.find((p) => fs.existsSync(p)) || null;
}
function cmdInstall(names) {
fs.mkdirSync(SKILLS_DIR, { recursive: true });
for (const name of names) {
const src = resolveMasterDir(name);
if (!src) {
console.log(`${name} — not found in prebuilt/ (tried "${name}" and "master-${name}")`);
continue;
}
const dirName = path.basename(src); // master-zhiyi
const dest = path.join(SKILLS_DIR, dirName);
cpR(src, dest);
console.log(`${name}${dest}`);
}
}
function cmdUninstall(names) {
for (const name of names) {
// Try both prefixed and bare directory names for backward compatibility
// with any pre-v0.6 installs that may still sit at ~/.claude/skills/<slug>/.
const candidates = [
path.join(SKILLS_DIR, name), // exact: master-zhiyi
path.join(SKILLS_DIR, `master-${name}`), // short: zhiyi → master-zhiyi
];
const dest = candidates.find((p) => fs.existsSync(p));
if (!dest) {
console.log(`${name} — not installed`);
continue;
}
fs.rmSync(dest, { recursive: true, force: true });
console.log(`${name} removed (${dest})`);
}
}
function showHelp() {
console.log(`
master-skill — Buddhist Master AI Skills installer (v0.6+)
Usage:
master-skill install <name...> Install masters to ~/.claude/skills/
master-skill install --all Install all available masters
master-skill list List available masters
master-skill uninstall <name...> Remove installed masters
master-skill --help Show this help
Names accept both short (zhiyi) and full (master-zhiyi) forms.
Slash commands are always /master-<slug> (e.g. /master-zhiyi).
Examples:
npx master-skill install zhiyi fazang
npx master-skill install master-milarepa master-tsongkhapa
npx master-skill install --all
npx master-skill list
npx master-skill uninstall zhiyi
`);
}
// --- main ---
const args = process.argv.slice(2);
const cmd = args[0];
if (!cmd || cmd === "--help" || cmd === "-h") {
showHelp();
} else if (cmd === "list") {
cmdList();
} else if (cmd === "install") {
const rest = args.slice(1);
if (rest.includes("--all")) {
const all = availableMasters().map((m) => m.name);
if (!all.length) {
console.log("No masters available.");
} else {
console.log(`Installing all ${all.length} masters...\n`);
cmdInstall(all);
}
} else if (rest.length === 0) {
console.log("Usage: master-skill install <name...> | --all");
} else {
cmdInstall(rest);
}
} else if (cmd === "uninstall") {
const rest = args.slice(1);
if (rest.length === 0) {
console.log("Usage: master-skill uninstall <name...>");
} else {
cmdUninstall(rest);
}
} else {
console.log(`Unknown command: ${cmd}\nRun master-skill --help for usage.`);
process.exit(1);
}