refactor(installer): discover skills by SKILL.md instead of manifest YAML
Switch skill discovery gate from requiring bmad-skill-manifest.yaml with type: skill to detecting any directory with a valid SKILL.md (frontmatter name + description, name matches directory name). Delete 34 stub manifests that carried no data beyond type: skill. Agent manifests (9) are retained for persona metadata consumed by agent-manifest.csv.
This commit is contained in:
parent
c28206dca4
commit
02879f7b6b
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
type: skill
|
||||
module: core
|
||||
capabilities:
|
||||
- name: bmad-distillator
|
||||
menu-code: DSTL
|
||||
description: "Produces lossless LLM-optimized distillate from source documents. Use after producing large human presentable documents that will be consumed later by LLMs"
|
||||
supports-headless: true
|
||||
input: source documents
|
||||
args: output, validate
|
||||
output: single distillate or folder of distillates next to source input
|
||||
config-vars-used: null
|
||||
phase: anytime
|
||||
before: []
|
||||
after: []
|
||||
is-required: false
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -1 +0,0 @@
|
|||
type: skill
|
||||
|
|
@ -78,7 +78,6 @@ async function createTestBmadFixture() {
|
|||
'You are a test agent.',
|
||||
].join('\n'),
|
||||
);
|
||||
await fs.writeFile(path.join(skillDir, 'bmad-skill-manifest.yaml'), 'SKILL.md:\n type: skill\n');
|
||||
await fs.writeFile(path.join(skillDir, 'workflow.md'), '# Test Workflow\nStep 1: Do the thing.\n');
|
||||
|
||||
return fixtureDir;
|
||||
|
|
@ -1535,7 +1534,6 @@ async function runTests() {
|
|||
// --- Skill at unusual path: core/custom-area/my-skill/ ---
|
||||
const skillDir29 = path.join(tempFixture29, 'core', 'custom-area', 'my-skill');
|
||||
await fs.ensureDir(skillDir29);
|
||||
await fs.writeFile(path.join(skillDir29, 'bmad-skill-manifest.yaml'), 'type: skill\n');
|
||||
await fs.writeFile(
|
||||
path.join(skillDir29, 'SKILL.md'),
|
||||
'---\nname: my-skill\ndescription: A skill at an unusual path\n---\n\nFollow the instructions in [workflow.md](workflow.md).\n',
|
||||
|
|
@ -1554,7 +1552,6 @@ async function runTests() {
|
|||
// --- Skill inside workflows/ dir: core/workflows/wf-skill/ (exercises findWorkflows skip logic) ---
|
||||
const wfSkillDir29 = path.join(tempFixture29, 'core', 'workflows', 'wf-skill');
|
||||
await fs.ensureDir(wfSkillDir29);
|
||||
await fs.writeFile(path.join(wfSkillDir29, 'bmad-skill-manifest.yaml'), 'type: skill\n');
|
||||
await fs.writeFile(
|
||||
path.join(wfSkillDir29, 'SKILL.md'),
|
||||
'---\nname: wf-skill\ndescription: A skill inside workflows dir\n---\n\nFollow the instructions in [workflow.md](workflow.md).\n',
|
||||
|
|
@ -1564,7 +1561,6 @@ async function runTests() {
|
|||
// --- Skill inside tasks/ dir: core/tasks/task-skill/ ---
|
||||
const taskSkillDir29 = path.join(tempFixture29, 'core', 'tasks', 'task-skill');
|
||||
await fs.ensureDir(taskSkillDir29);
|
||||
await fs.writeFile(path.join(taskSkillDir29, 'bmad-skill-manifest.yaml'), 'type: skill\n');
|
||||
await fs.writeFile(
|
||||
path.join(taskSkillDir29, 'SKILL.md'),
|
||||
'---\nname: task-skill\ndescription: A skill inside tasks dir\n---\n\nFollow the instructions in [workflow.md](workflow.md).\n',
|
||||
|
|
@ -1636,7 +1632,6 @@ async function runTests() {
|
|||
// Test scanInstalledModules recognizes skill-only modules
|
||||
const skillOnlyModDir29 = path.join(tempFixture29, 'skill-only-mod');
|
||||
await fs.ensureDir(path.join(skillOnlyModDir29, 'deep', 'nested', 'my-skill'));
|
||||
await fs.writeFile(path.join(skillOnlyModDir29, 'deep', 'nested', 'my-skill', 'bmad-skill-manifest.yaml'), 'type: skill\n');
|
||||
await fs.writeFile(
|
||||
path.join(skillOnlyModDir29, 'deep', 'nested', 'my-skill', 'SKILL.md'),
|
||||
'---\nname: my-skill\ndescription: desc\n---\n\nFollow the instructions in [workflow.md](workflow.md).\n',
|
||||
|
|
|
|||
|
|
@ -50,29 +50,6 @@ class ManifestGenerator {
|
|||
return getInstallToBmadShared(manifest, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Native SKILL.md entrypoints can be packaged as either skills or agents.
|
||||
* Both need verbatim installation for skill-format IDEs.
|
||||
* @param {string|null} artifactType - Manifest type resolved for SKILL.md
|
||||
* @returns {boolean} True when the directory should be installed verbatim
|
||||
*/
|
||||
isNativeSkillDirType(artifactType) {
|
||||
return artifactType === 'skill' || artifactType === 'agent';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a loaded bmad-skill-manifest.yaml declares a native
|
||||
* SKILL.md entrypoint, either as a single-entry manifest or a multi-entry map.
|
||||
* @param {Object|null} manifest - Loaded manifest
|
||||
* @returns {boolean} True when the manifest contains a native skill/agent entrypoint
|
||||
*/
|
||||
hasNativeSkillManifest(manifest) {
|
||||
if (!manifest) return false;
|
||||
if (manifest.__single) return this.isNativeSkillDirType(manifest.__single.type);
|
||||
|
||||
return Object.values(manifest).some((entry) => this.isNativeSkillDirType(entry?.type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean text for CSV output by normalizing whitespace.
|
||||
* Note: Quote escaping is handled by escapeCsv() at write time.
|
||||
|
|
@ -170,9 +147,9 @@ class ManifestGenerator {
|
|||
|
||||
/**
|
||||
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
|
||||
* A native entrypoint directory is one that contains both a
|
||||
* bmad-skill-manifest.yaml with type: skill or type: agent AND a SKILL.md file
|
||||
* with name/description frontmatter.
|
||||
* A directory is discovered as a skill when it contains a SKILL.md file with
|
||||
* valid name/description frontmatter (name must match directory name).
|
||||
* Manifest YAML is loaded only when present — for install_to_bmad and agent metadata.
|
||||
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
||||
*/
|
||||
async collectSkills() {
|
||||
|
|
@ -193,21 +170,18 @@ class ManifestGenerator {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check this directory for skill manifest
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
|
||||
// Determine if this directory is a native SKILL.md entrypoint
|
||||
// SKILL.md with valid frontmatter is the primary discovery gate
|
||||
const skillFile = 'SKILL.md';
|
||||
const artifactType = this.getArtifactType(manifest, skillFile);
|
||||
|
||||
if (this.isNativeSkillDirType(artifactType)) {
|
||||
const skillMdPath = path.join(dir, 'SKILL.md');
|
||||
const skillMdPath = path.join(dir, skillFile);
|
||||
const dirName = path.basename(dir);
|
||||
|
||||
// Validate and parse SKILL.md
|
||||
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
||||
|
||||
if (skillMeta) {
|
||||
// Load manifest when present (for install_to_bmad and agent metadata)
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
const artifactType = this.getArtifactType(manifest, skillFile);
|
||||
|
||||
// Build path relative from module root (points to SKILL.md — the permanent entrypoint)
|
||||
const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
|
||||
const installPath = relativePath
|
||||
|
|
@ -247,25 +221,6 @@ class ManifestGenerator {
|
|||
console.log(`[DEBUG] collectSkills: claimed skill "${skillMeta.name}" as ${canonicalId} at ${dir}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if manifest says this is a native entrypoint but the directory was not claimed
|
||||
if (manifest && !this.skillClaimedDirs.has(dir)) {
|
||||
let hasNativeSkillType = false;
|
||||
if (manifest.__single) {
|
||||
hasNativeSkillType = this.isNativeSkillDirType(manifest.__single.type);
|
||||
} else {
|
||||
for (const key of Object.keys(manifest)) {
|
||||
if (this.isNativeSkillDirType(manifest[key]?.type)) {
|
||||
hasNativeSkillType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasNativeSkillType && debug) {
|
||||
console.log(`[DEBUG] collectSkills: dir has native SKILL.md manifest but failed validation: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into subdirectories
|
||||
for (const entry of entries) {
|
||||
|
|
@ -1384,11 +1339,10 @@ class ManifestGenerator {
|
|||
const hasTasks = await fs.pathExists(path.join(modulePath, 'tasks'));
|
||||
const hasTools = await fs.pathExists(path.join(modulePath, 'tools'));
|
||||
|
||||
// Check for native-entrypoint-only modules: recursive scan for
|
||||
// bmad-skill-manifest.yaml with type: skill or type: agent
|
||||
// Check for native-entrypoint-only modules: recursive scan for SKILL.md
|
||||
let hasSkills = false;
|
||||
if (!hasAgents && !hasWorkflows && !hasTasks && !hasTools) {
|
||||
hasSkills = await this._hasSkillManifestRecursive(modulePath);
|
||||
hasSkills = await this._hasSkillMdRecursive(modulePath);
|
||||
}
|
||||
|
||||
// If it has any of these directories or skill manifests, it's likely a module
|
||||
|
|
@ -1404,13 +1358,12 @@ class ManifestGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively check if a directory tree contains a bmad-skill-manifest.yaml that
|
||||
* declares a native SKILL.md entrypoint (type: skill or type: agent).
|
||||
* Recursively check if a directory tree contains a SKILL.md file.
|
||||
* Skips directories starting with . or _.
|
||||
* @param {string} dir - Directory to search
|
||||
* @returns {boolean} True if a skill manifest is found
|
||||
* @returns {boolean} True if a SKILL.md is found
|
||||
*/
|
||||
async _hasSkillManifestRecursive(dir) {
|
||||
async _hasSkillMdRecursive(dir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
|
@ -1418,15 +1371,14 @@ class ManifestGenerator {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check for manifest in this directory
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
if (this.hasNativeSkillManifest(manifest)) return true;
|
||||
// Check for SKILL.md in this directory
|
||||
if (entries.some((e) => !e.isDirectory() && e.name === 'SKILL.md')) return true;
|
||||
|
||||
// Recurse into subdirectories
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
|
||||
if (await this._hasSkillManifestRecursive(path.join(dir, entry.name))) return true;
|
||||
if (await this._hasSkillMdRecursive(path.join(dir, entry.name))) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue