bmad-method 6.2.1-next.25 → 6.2.1-next.27
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/package.json +1 -1
- package/tools/cli/installers/lib/core/installer.js +1 -3
- package/tools/cli/installers/lib/core/manifest-generator.js +58 -734
- package/src/bmm-skills/1-analysis/bmad-document-project/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-dev-story/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/bmad-skill-manifest.yaml +0 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-advanced-elicitation/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-brainstorming/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-distillator/bmad-skill-manifest.yaml +0 -15
- package/src/core-skills/bmad-editorial-review-prose/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-editorial-review-structure/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-help/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-index-docs/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-init/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-party-mode/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-review-adversarial-general/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-review-edge-case-hunter/bmad-skill-manifest.yaml +0 -1
- package/src/core-skills/bmad-shard-doc/bmad-skill-manifest.yaml +0 -1
package/package.json
CHANGED
|
@@ -1124,11 +1124,9 @@ class Installer {
|
|
|
1124
1124
|
// Pre-register manifest files
|
|
1125
1125
|
const cfgDir = path.join(bmadDir, '_config');
|
|
1126
1126
|
this.installedFiles.add(path.join(cfgDir, 'manifest.yaml'));
|
|
1127
|
-
this.installedFiles.add(path.join(cfgDir, 'workflow-manifest.csv'));
|
|
1128
1127
|
this.installedFiles.add(path.join(cfgDir, 'agent-manifest.csv'));
|
|
1129
|
-
this.installedFiles.add(path.join(cfgDir, 'task-manifest.csv'));
|
|
1130
1128
|
|
|
1131
|
-
// Generate CSV manifests for
|
|
1129
|
+
// Generate CSV manifests for agents, skills AND ALL FILES with hashes
|
|
1132
1130
|
// This must happen BEFORE mergeModuleHelpCatalogs because it depends on agent-manifest.csv
|
|
1133
1131
|
message('Generating manifests...');
|
|
1134
1132
|
const manifestGen = new ManifestGenerator();
|
|
@@ -16,15 +16,12 @@ const {
|
|
|
16
16
|
const packageJson = require('../../../../../package.json');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Generates manifest files for installed
|
|
19
|
+
* Generates manifest files for installed skills and agents
|
|
20
20
|
*/
|
|
21
21
|
class ManifestGenerator {
|
|
22
22
|
constructor() {
|
|
23
|
-
this.workflows = [];
|
|
24
23
|
this.skills = [];
|
|
25
24
|
this.agents = [];
|
|
26
|
-
this.tasks = [];
|
|
27
|
-
this.tools = [];
|
|
28
25
|
this.modules = [];
|
|
29
26
|
this.files = [];
|
|
30
27
|
this.selectedIdes = [];
|
|
@@ -50,29 +47,6 @@ class ManifestGenerator {
|
|
|
50
47
|
return getInstallToBmadShared(manifest, filename);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
/**
|
|
54
|
-
* Native SKILL.md entrypoints can be packaged as either skills or agents.
|
|
55
|
-
* Both need verbatim installation for skill-format IDEs.
|
|
56
|
-
* @param {string|null} artifactType - Manifest type resolved for SKILL.md
|
|
57
|
-
* @returns {boolean} True when the directory should be installed verbatim
|
|
58
|
-
*/
|
|
59
|
-
isNativeSkillDirType(artifactType) {
|
|
60
|
-
return artifactType === 'skill' || artifactType === 'agent';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Check whether a loaded bmad-skill-manifest.yaml declares a native
|
|
65
|
-
* SKILL.md entrypoint, either as a single-entry manifest or a multi-entry map.
|
|
66
|
-
* @param {Object|null} manifest - Loaded manifest
|
|
67
|
-
* @returns {boolean} True when the manifest contains a native skill/agent entrypoint
|
|
68
|
-
*/
|
|
69
|
-
hasNativeSkillManifest(manifest) {
|
|
70
|
-
if (!manifest) return false;
|
|
71
|
-
if (manifest.__single) return this.isNativeSkillDirType(manifest.__single.type);
|
|
72
|
-
|
|
73
|
-
return Object.values(manifest).some((entry) => this.isNativeSkillDirType(entry?.type));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
50
|
/**
|
|
77
51
|
* Clean text for CSV output by normalizing whitespace.
|
|
78
52
|
* Note: Quote escaping is handled by escapeCsv() at write time.
|
|
@@ -108,10 +82,6 @@ class ManifestGenerator {
|
|
|
108
82
|
this.modules = allModules;
|
|
109
83
|
this.updatedModules = allModules; // Include ALL modules (including custom) for scanning
|
|
110
84
|
|
|
111
|
-
// For CSV manifests, we need to include ALL modules that are installed
|
|
112
|
-
// preservedModules controls which modules stay as-is in the CSV (don't get rescanned)
|
|
113
|
-
// But all modules should be included in the final manifest
|
|
114
|
-
this.preservedModules = allModules; // Include ALL modules (including custom)
|
|
115
85
|
this.bmadDir = bmadDir;
|
|
116
86
|
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
|
|
117
87
|
this.allInstalledFiles = installedFiles;
|
|
@@ -134,35 +104,20 @@ class ManifestGenerator {
|
|
|
134
104
|
// Collect skills first (populates skillClaimedDirs before legacy collectors run)
|
|
135
105
|
await this.collectSkills();
|
|
136
106
|
|
|
137
|
-
// Collect workflow data
|
|
138
|
-
await this.collectWorkflows(selectedModules);
|
|
139
|
-
|
|
140
107
|
// Collect agent data - use updatedModules which includes all installed modules
|
|
141
108
|
await this.collectAgents(this.updatedModules);
|
|
142
109
|
|
|
143
|
-
// Collect task data
|
|
144
|
-
await this.collectTasks(this.updatedModules);
|
|
145
|
-
|
|
146
|
-
// Collect tool data
|
|
147
|
-
await this.collectTools(this.updatedModules);
|
|
148
|
-
|
|
149
110
|
// Write manifest files and collect their paths
|
|
150
111
|
const manifestFiles = [
|
|
151
112
|
await this.writeMainManifest(cfgDir),
|
|
152
|
-
await this.writeWorkflowManifest(cfgDir),
|
|
153
113
|
await this.writeSkillManifest(cfgDir),
|
|
154
114
|
await this.writeAgentManifest(cfgDir),
|
|
155
|
-
await this.writeTaskManifest(cfgDir),
|
|
156
|
-
await this.writeToolManifest(cfgDir),
|
|
157
115
|
await this.writeFilesManifest(cfgDir),
|
|
158
116
|
];
|
|
159
117
|
|
|
160
118
|
return {
|
|
161
119
|
skills: this.skills.length,
|
|
162
|
-
workflows: this.workflows.length,
|
|
163
120
|
agents: this.agents.length,
|
|
164
|
-
tasks: this.tasks.length,
|
|
165
|
-
tools: this.tools.length,
|
|
166
121
|
files: this.files.length,
|
|
167
122
|
manifestFiles: manifestFiles,
|
|
168
123
|
};
|
|
@@ -170,9 +125,9 @@ class ManifestGenerator {
|
|
|
170
125
|
|
|
171
126
|
/**
|
|
172
127
|
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
|
|
173
|
-
* A
|
|
174
|
-
*
|
|
175
|
-
*
|
|
128
|
+
* A directory is discovered as a skill when it contains a SKILL.md file with
|
|
129
|
+
* valid name/description frontmatter (name must match directory name).
|
|
130
|
+
* Manifest YAML is loaded only when present — for install_to_bmad and agent metadata.
|
|
176
131
|
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
|
177
132
|
*/
|
|
178
133
|
async collectSkills() {
|
|
@@ -193,77 +148,55 @@ class ManifestGenerator {
|
|
|
193
148
|
return;
|
|
194
149
|
}
|
|
195
150
|
|
|
196
|
-
//
|
|
197
|
-
const manifest = await this.loadSkillManifest(dir);
|
|
198
|
-
|
|
199
|
-
// Determine if this directory is a native SKILL.md entrypoint
|
|
151
|
+
// SKILL.md with valid frontmatter is the primary discovery gate
|
|
200
152
|
const skillFile = 'SKILL.md';
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
const canonicalId = dirName;
|
|
226
|
-
|
|
227
|
-
this.skills.push({
|
|
228
|
-
name: skillMeta.name,
|
|
229
|
-
description: this.cleanForCSV(skillMeta.description),
|
|
230
|
-
module: moduleName,
|
|
231
|
-
path: installPath,
|
|
232
|
-
canonicalId,
|
|
233
|
-
install_to_bmad: this.getInstallToBmad(manifest, skillFile),
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Add to files list
|
|
237
|
-
this.files.push({
|
|
238
|
-
type: 'skill',
|
|
239
|
-
name: skillMeta.name,
|
|
240
|
-
module: moduleName,
|
|
241
|
-
path: installPath,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
this.skillClaimedDirs.add(dir);
|
|
245
|
-
|
|
246
|
-
if (debug) {
|
|
247
|
-
console.log(`[DEBUG] collectSkills: claimed skill "${skillMeta.name}" as ${canonicalId} at ${dir}`);
|
|
248
|
-
}
|
|
153
|
+
const skillMdPath = path.join(dir, skillFile);
|
|
154
|
+
const dirName = path.basename(dir);
|
|
155
|
+
|
|
156
|
+
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
|
157
|
+
|
|
158
|
+
if (skillMeta) {
|
|
159
|
+
// Load manifest when present (for install_to_bmad and agent metadata)
|
|
160
|
+
const manifest = await this.loadSkillManifest(dir);
|
|
161
|
+
const artifactType = this.getArtifactType(manifest, skillFile);
|
|
162
|
+
|
|
163
|
+
// Build path relative from module root (points to SKILL.md — the permanent entrypoint)
|
|
164
|
+
const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
|
|
165
|
+
const installPath = relativePath
|
|
166
|
+
? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
|
|
167
|
+
: `${this.bmadFolderName}/${moduleName}/${skillFile}`;
|
|
168
|
+
|
|
169
|
+
// Native SKILL.md entrypoints derive canonicalId from directory name.
|
|
170
|
+
// Agent entrypoints may keep canonicalId metadata for compatibility, so
|
|
171
|
+
// only warn for non-agent SKILL.md directories.
|
|
172
|
+
if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
|
|
173
|
+
console.warn(
|
|
174
|
+
`Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
|
|
175
|
+
);
|
|
249
176
|
}
|
|
250
|
-
|
|
177
|
+
const canonicalId = dirName;
|
|
251
178
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
179
|
+
this.skills.push({
|
|
180
|
+
name: skillMeta.name,
|
|
181
|
+
description: this.cleanForCSV(skillMeta.description),
|
|
182
|
+
module: moduleName,
|
|
183
|
+
path: installPath,
|
|
184
|
+
canonicalId,
|
|
185
|
+
install_to_bmad: this.getInstallToBmad(manifest, skillFile),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Add to files list
|
|
189
|
+
this.files.push({
|
|
190
|
+
type: 'skill',
|
|
191
|
+
name: skillMeta.name,
|
|
192
|
+
module: moduleName,
|
|
193
|
+
path: installPath,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this.skillClaimedDirs.add(dir);
|
|
197
|
+
|
|
198
|
+
if (debug) {
|
|
199
|
+
console.log(`[DEBUG] collectSkills: claimed skill "${skillMeta.name}" as ${canonicalId} at ${dir}`);
|
|
267
200
|
}
|
|
268
201
|
}
|
|
269
202
|
|
|
@@ -334,153 +267,6 @@ class ManifestGenerator {
|
|
|
334
267
|
}
|
|
335
268
|
}
|
|
336
269
|
|
|
337
|
-
/**
|
|
338
|
-
* Collect all workflows from core and selected modules
|
|
339
|
-
* Scans the INSTALLED bmad directory, not the source
|
|
340
|
-
*/
|
|
341
|
-
async collectWorkflows(selectedModules) {
|
|
342
|
-
this.workflows = [];
|
|
343
|
-
|
|
344
|
-
// Use updatedModules which already includes deduplicated 'core' + selectedModules
|
|
345
|
-
for (const moduleName of this.updatedModules) {
|
|
346
|
-
const modulePath = path.join(this.bmadDir, moduleName);
|
|
347
|
-
|
|
348
|
-
if (await fs.pathExists(modulePath)) {
|
|
349
|
-
const moduleWorkflows = await this.getWorkflowsFromPath(modulePath, moduleName);
|
|
350
|
-
this.workflows.push(...moduleWorkflows);
|
|
351
|
-
|
|
352
|
-
// Also scan tasks/ for type:skill entries (skills can live anywhere)
|
|
353
|
-
const tasksSkills = await this.getWorkflowsFromPath(modulePath, moduleName, 'tasks');
|
|
354
|
-
this.workflows.push(...tasksSkills);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Recursively find and parse workflow.md files
|
|
361
|
-
*/
|
|
362
|
-
async getWorkflowsFromPath(basePath, moduleName, subDir = 'workflows') {
|
|
363
|
-
const workflows = [];
|
|
364
|
-
const workflowsPath = path.join(basePath, subDir);
|
|
365
|
-
const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
|
|
366
|
-
|
|
367
|
-
if (debug) {
|
|
368
|
-
console.log(`[DEBUG] Scanning workflows in: ${workflowsPath}`);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (!(await fs.pathExists(workflowsPath))) {
|
|
372
|
-
if (debug) {
|
|
373
|
-
console.log(`[DEBUG] Workflows path does not exist: ${workflowsPath}`);
|
|
374
|
-
}
|
|
375
|
-
return workflows;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Recursively find workflow.md files
|
|
379
|
-
const findWorkflows = async (dir, relativePath = '') => {
|
|
380
|
-
// Skip directories already claimed as skills
|
|
381
|
-
if (this.skillClaimedDirs && this.skillClaimedDirs.has(dir)) return;
|
|
382
|
-
|
|
383
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
384
|
-
// Load skill manifest for this directory (if present)
|
|
385
|
-
const skillManifest = await this.loadSkillManifest(dir);
|
|
386
|
-
|
|
387
|
-
for (const entry of entries) {
|
|
388
|
-
const fullPath = path.join(dir, entry.name);
|
|
389
|
-
|
|
390
|
-
if (entry.isDirectory()) {
|
|
391
|
-
// Skip directories claimed by collectSkills
|
|
392
|
-
if (this.skillClaimedDirs && this.skillClaimedDirs.has(fullPath)) continue;
|
|
393
|
-
// Recurse into subdirectories
|
|
394
|
-
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
395
|
-
await findWorkflows(fullPath, newRelativePath);
|
|
396
|
-
} else if (entry.name === 'workflow.md' || (entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))) {
|
|
397
|
-
// Parse workflow file (both YAML and MD formats)
|
|
398
|
-
if (debug) {
|
|
399
|
-
console.log(`[DEBUG] Found workflow file: ${fullPath}`);
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
// Read and normalize line endings (fix Windows CRLF issues)
|
|
403
|
-
const rawContent = await fs.readFile(fullPath, 'utf8');
|
|
404
|
-
const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
405
|
-
|
|
406
|
-
// Parse MD workflow with YAML frontmatter
|
|
407
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
408
|
-
if (!frontmatterMatch) {
|
|
409
|
-
if (debug) {
|
|
410
|
-
console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
|
|
411
|
-
}
|
|
412
|
-
continue; // Skip MD files without frontmatter
|
|
413
|
-
}
|
|
414
|
-
const workflow = yaml.parse(frontmatterMatch[1]);
|
|
415
|
-
|
|
416
|
-
if (debug) {
|
|
417
|
-
console.log(`[DEBUG] Parsed: name="${workflow.name}", description=${workflow.description ? 'OK' : 'MISSING'}`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Skip template workflows (those with placeholder values)
|
|
421
|
-
if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) {
|
|
422
|
-
if (debug) {
|
|
423
|
-
console.log(`[DEBUG] Skipped (template placeholder): ${workflow.name}`);
|
|
424
|
-
}
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Skip workflows marked as non-standalone (reference/example workflows)
|
|
429
|
-
if (workflow.standalone === false) {
|
|
430
|
-
if (debug) {
|
|
431
|
-
console.log(`[DEBUG] Skipped (standalone=false): ${workflow.name}`);
|
|
432
|
-
}
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (workflow.name && workflow.description) {
|
|
437
|
-
// Build relative path for installation
|
|
438
|
-
const installPath =
|
|
439
|
-
moduleName === 'core'
|
|
440
|
-
? `${this.bmadFolderName}/core/${subDir}/${relativePath}/${entry.name}`
|
|
441
|
-
: `${this.bmadFolderName}/${moduleName}/${subDir}/${relativePath}/${entry.name}`;
|
|
442
|
-
|
|
443
|
-
// Workflows with standalone: false are filtered out above
|
|
444
|
-
workflows.push({
|
|
445
|
-
name: workflow.name,
|
|
446
|
-
description: this.cleanForCSV(workflow.description),
|
|
447
|
-
module: moduleName,
|
|
448
|
-
path: installPath,
|
|
449
|
-
canonicalId: this.getCanonicalId(skillManifest, entry.name),
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// Add to files list
|
|
453
|
-
this.files.push({
|
|
454
|
-
type: 'workflow',
|
|
455
|
-
name: workflow.name,
|
|
456
|
-
module: moduleName,
|
|
457
|
-
path: installPath,
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
if (debug) {
|
|
461
|
-
console.log(`[DEBUG] ✓ Added workflow: ${workflow.name} (${moduleName})`);
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
if (debug) {
|
|
465
|
-
console.log(`[DEBUG] Skipped (missing name or description): ${fullPath}`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
} catch (error) {
|
|
469
|
-
await prompts.log.warn(`Failed to parse workflow at ${fullPath}: ${error.message}`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
await findWorkflows(workflowsPath);
|
|
476
|
-
|
|
477
|
-
if (debug) {
|
|
478
|
-
console.log(`[DEBUG] Total workflows found in ${moduleName}: ${workflows.length}`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return workflows;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
270
|
/**
|
|
485
271
|
* Collect all agents from core and selected modules
|
|
486
272
|
* Scans the INSTALLED bmad directory, not the source
|
|
@@ -634,212 +420,6 @@ class ManifestGenerator {
|
|
|
634
420
|
return agents;
|
|
635
421
|
}
|
|
636
422
|
|
|
637
|
-
/**
|
|
638
|
-
* Collect all tasks from core and selected modules
|
|
639
|
-
* Scans the INSTALLED bmad directory, not the source
|
|
640
|
-
*/
|
|
641
|
-
async collectTasks(selectedModules) {
|
|
642
|
-
this.tasks = [];
|
|
643
|
-
|
|
644
|
-
// Use updatedModules which already includes deduplicated 'core' + selectedModules
|
|
645
|
-
for (const moduleName of this.updatedModules) {
|
|
646
|
-
const tasksPath = path.join(this.bmadDir, moduleName, 'tasks');
|
|
647
|
-
|
|
648
|
-
if (await fs.pathExists(tasksPath)) {
|
|
649
|
-
const moduleTasks = await this.getTasksFromDir(tasksPath, moduleName);
|
|
650
|
-
this.tasks.push(...moduleTasks);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Get tasks from a directory
|
|
657
|
-
*/
|
|
658
|
-
async getTasksFromDir(dirPath, moduleName) {
|
|
659
|
-
// Skip directories claimed by collectSkills
|
|
660
|
-
if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
|
|
661
|
-
const tasks = [];
|
|
662
|
-
const files = await fs.readdir(dirPath);
|
|
663
|
-
// Load skill manifest for this directory (if present)
|
|
664
|
-
const skillManifest = await this.loadSkillManifest(dirPath);
|
|
665
|
-
|
|
666
|
-
for (const file of files) {
|
|
667
|
-
// Check for both .xml and .md files
|
|
668
|
-
if (file.endsWith('.xml') || file.endsWith('.md')) {
|
|
669
|
-
const filePath = path.join(dirPath, file);
|
|
670
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
671
|
-
|
|
672
|
-
// Skip internal/engine files (not user-facing tasks)
|
|
673
|
-
if (content.includes('internal="true"')) {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
let name = file.replace(/\.(xml|md)$/, '');
|
|
678
|
-
let displayName = name;
|
|
679
|
-
let description = '';
|
|
680
|
-
let standalone = false;
|
|
681
|
-
|
|
682
|
-
if (file.endsWith('.md')) {
|
|
683
|
-
// Parse YAML frontmatter for .md tasks
|
|
684
|
-
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
685
|
-
if (frontmatterMatch) {
|
|
686
|
-
try {
|
|
687
|
-
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
688
|
-
name = frontmatter.name || name;
|
|
689
|
-
displayName = frontmatter.displayName || frontmatter.name || name;
|
|
690
|
-
description = this.cleanForCSV(frontmatter.description || '');
|
|
691
|
-
// Tasks are standalone by default unless explicitly false (internal=true is already filtered above)
|
|
692
|
-
standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
|
|
693
|
-
} catch {
|
|
694
|
-
// If YAML parsing fails, use defaults
|
|
695
|
-
standalone = true; // Default to standalone
|
|
696
|
-
}
|
|
697
|
-
} else {
|
|
698
|
-
standalone = true; // No frontmatter means standalone
|
|
699
|
-
}
|
|
700
|
-
} else {
|
|
701
|
-
// For .xml tasks, extract from tag attributes
|
|
702
|
-
const nameMatch = content.match(/name="([^"]+)"/);
|
|
703
|
-
displayName = nameMatch ? nameMatch[1] : name;
|
|
704
|
-
|
|
705
|
-
const descMatch = content.match(/description="([^"]+)"/);
|
|
706
|
-
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
|
707
|
-
description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
|
|
708
|
-
|
|
709
|
-
const standaloneFalseMatch = content.match(/<task[^>]+standalone="false"/);
|
|
710
|
-
standalone = !standaloneFalseMatch;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Build relative path for installation
|
|
714
|
-
const installPath =
|
|
715
|
-
moduleName === 'core' ? `${this.bmadFolderName}/core/tasks/${file}` : `${this.bmadFolderName}/${moduleName}/tasks/${file}`;
|
|
716
|
-
|
|
717
|
-
tasks.push({
|
|
718
|
-
name: name,
|
|
719
|
-
displayName: displayName,
|
|
720
|
-
description: description,
|
|
721
|
-
module: moduleName,
|
|
722
|
-
path: installPath,
|
|
723
|
-
standalone: standalone,
|
|
724
|
-
canonicalId: this.getCanonicalId(skillManifest, file),
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
// Add to files list
|
|
728
|
-
this.files.push({
|
|
729
|
-
type: 'task',
|
|
730
|
-
name: name,
|
|
731
|
-
module: moduleName,
|
|
732
|
-
path: installPath,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return tasks;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Collect all tools from core and selected modules
|
|
742
|
-
* Scans the INSTALLED bmad directory, not the source
|
|
743
|
-
*/
|
|
744
|
-
async collectTools(selectedModules) {
|
|
745
|
-
this.tools = [];
|
|
746
|
-
|
|
747
|
-
// Use updatedModules which already includes deduplicated 'core' + selectedModules
|
|
748
|
-
for (const moduleName of this.updatedModules) {
|
|
749
|
-
const toolsPath = path.join(this.bmadDir, moduleName, 'tools');
|
|
750
|
-
|
|
751
|
-
if (await fs.pathExists(toolsPath)) {
|
|
752
|
-
const moduleTools = await this.getToolsFromDir(toolsPath, moduleName);
|
|
753
|
-
this.tools.push(...moduleTools);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Get tools from a directory
|
|
760
|
-
*/
|
|
761
|
-
async getToolsFromDir(dirPath, moduleName) {
|
|
762
|
-
// Skip directories claimed by collectSkills
|
|
763
|
-
if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
|
|
764
|
-
const tools = [];
|
|
765
|
-
const files = await fs.readdir(dirPath);
|
|
766
|
-
// Load skill manifest for this directory (if present)
|
|
767
|
-
const skillManifest = await this.loadSkillManifest(dirPath);
|
|
768
|
-
|
|
769
|
-
for (const file of files) {
|
|
770
|
-
// Check for both .xml and .md files
|
|
771
|
-
if (file.endsWith('.xml') || file.endsWith('.md')) {
|
|
772
|
-
const filePath = path.join(dirPath, file);
|
|
773
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
774
|
-
|
|
775
|
-
// Skip internal tools (same as tasks)
|
|
776
|
-
if (content.includes('internal="true"')) {
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
let name = file.replace(/\.(xml|md)$/, '');
|
|
781
|
-
let displayName = name;
|
|
782
|
-
let description = '';
|
|
783
|
-
let standalone = false;
|
|
784
|
-
|
|
785
|
-
if (file.endsWith('.md')) {
|
|
786
|
-
// Parse YAML frontmatter for .md tools
|
|
787
|
-
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
788
|
-
if (frontmatterMatch) {
|
|
789
|
-
try {
|
|
790
|
-
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
791
|
-
name = frontmatter.name || name;
|
|
792
|
-
displayName = frontmatter.displayName || frontmatter.name || name;
|
|
793
|
-
description = this.cleanForCSV(frontmatter.description || '');
|
|
794
|
-
// Tools are standalone by default unless explicitly false (internal=true is already filtered above)
|
|
795
|
-
standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
|
|
796
|
-
} catch {
|
|
797
|
-
// If YAML parsing fails, use defaults
|
|
798
|
-
standalone = true; // Default to standalone
|
|
799
|
-
}
|
|
800
|
-
} else {
|
|
801
|
-
standalone = true; // No frontmatter means standalone
|
|
802
|
-
}
|
|
803
|
-
} else {
|
|
804
|
-
// For .xml tools, extract from tag attributes
|
|
805
|
-
const nameMatch = content.match(/name="([^"]+)"/);
|
|
806
|
-
displayName = nameMatch ? nameMatch[1] : name;
|
|
807
|
-
|
|
808
|
-
const descMatch = content.match(/description="([^"]+)"/);
|
|
809
|
-
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
|
810
|
-
description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
|
|
811
|
-
|
|
812
|
-
const standaloneFalseMatch = content.match(/<tool[^>]+standalone="false"/);
|
|
813
|
-
standalone = !standaloneFalseMatch;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Build relative path for installation
|
|
817
|
-
const installPath =
|
|
818
|
-
moduleName === 'core' ? `${this.bmadFolderName}/core/tools/${file}` : `${this.bmadFolderName}/${moduleName}/tools/${file}`;
|
|
819
|
-
|
|
820
|
-
tools.push({
|
|
821
|
-
name: name,
|
|
822
|
-
displayName: displayName,
|
|
823
|
-
description: description,
|
|
824
|
-
module: moduleName,
|
|
825
|
-
path: installPath,
|
|
826
|
-
standalone: standalone,
|
|
827
|
-
canonicalId: this.getCanonicalId(skillManifest, file),
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
// Add to files list
|
|
831
|
-
this.files.push({
|
|
832
|
-
type: 'tool',
|
|
833
|
-
name: name,
|
|
834
|
-
module: moduleName,
|
|
835
|
-
path: installPath,
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
return tools;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
423
|
/**
|
|
844
424
|
* Write main manifest as YAML with installation info only
|
|
845
425
|
* Fetches fresh version info for all modules
|
|
@@ -925,131 +505,6 @@ class ManifestGenerator {
|
|
|
925
505
|
return manifestPath;
|
|
926
506
|
}
|
|
927
507
|
|
|
928
|
-
/**
|
|
929
|
-
* Read existing CSV and preserve rows for modules NOT being updated
|
|
930
|
-
* @param {string} csvPath - Path to existing CSV file
|
|
931
|
-
* @param {number} moduleColumnIndex - Which column contains the module name (0-indexed)
|
|
932
|
-
* @param {Array<string>} expectedColumns - Expected column names in order
|
|
933
|
-
* @param {Object} defaultValues - Default values for missing columns
|
|
934
|
-
* @returns {Array} Preserved CSV rows (without header), upgraded to match expected columns
|
|
935
|
-
*/
|
|
936
|
-
async getPreservedCsvRows(csvPath, moduleColumnIndex, expectedColumns, defaultValues = {}) {
|
|
937
|
-
if (!(await fs.pathExists(csvPath)) || this.preservedModules.length === 0) {
|
|
938
|
-
return [];
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
try {
|
|
942
|
-
const content = await fs.readFile(csvPath, 'utf8');
|
|
943
|
-
const lines = content.trim().split('\n');
|
|
944
|
-
|
|
945
|
-
if (lines.length < 2) {
|
|
946
|
-
return []; // No data rows
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// Parse header to understand old schema
|
|
950
|
-
const header = lines[0];
|
|
951
|
-
const headerColumns = header.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
|
952
|
-
const oldColumns = headerColumns.map((c) => c.replaceAll(/^"|"$/g, ''));
|
|
953
|
-
|
|
954
|
-
// Skip header row for data
|
|
955
|
-
const dataRows = lines.slice(1);
|
|
956
|
-
const preservedRows = [];
|
|
957
|
-
|
|
958
|
-
for (const row of dataRows) {
|
|
959
|
-
// Simple CSV parsing (handles quoted values)
|
|
960
|
-
const columns = row.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
|
961
|
-
const cleanColumns = columns.map((c) => c.replaceAll(/^"|"$/g, ''));
|
|
962
|
-
|
|
963
|
-
const moduleValue = cleanColumns[moduleColumnIndex];
|
|
964
|
-
|
|
965
|
-
// Keep this row if it belongs to a preserved module
|
|
966
|
-
if (this.preservedModules.includes(moduleValue)) {
|
|
967
|
-
// Upgrade row to match expected schema
|
|
968
|
-
const upgradedRow = this.upgradeRowToSchema(cleanColumns, oldColumns, expectedColumns, defaultValues);
|
|
969
|
-
preservedRows.push(upgradedRow);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
return preservedRows;
|
|
974
|
-
} catch (error) {
|
|
975
|
-
await prompts.log.warn(`Failed to read existing CSV ${csvPath}: ${error.message}`);
|
|
976
|
-
return [];
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* Upgrade a CSV row from old schema to new schema
|
|
982
|
-
* @param {Array<string>} rowValues - Values from old row
|
|
983
|
-
* @param {Array<string>} oldColumns - Old column names
|
|
984
|
-
* @param {Array<string>} newColumns - New column names
|
|
985
|
-
* @param {Object} defaultValues - Default values for missing columns
|
|
986
|
-
* @returns {string} Upgraded CSV row
|
|
987
|
-
*/
|
|
988
|
-
upgradeRowToSchema(rowValues, oldColumns, newColumns, defaultValues) {
|
|
989
|
-
const upgradedValues = [];
|
|
990
|
-
|
|
991
|
-
for (const newCol of newColumns) {
|
|
992
|
-
const oldIndex = oldColumns.indexOf(newCol);
|
|
993
|
-
|
|
994
|
-
if (oldIndex !== -1 && oldIndex < rowValues.length) {
|
|
995
|
-
// Column exists in old schema, use its value
|
|
996
|
-
upgradedValues.push(rowValues[oldIndex]);
|
|
997
|
-
} else if (defaultValues[newCol] === undefined) {
|
|
998
|
-
// Column missing, no default provided
|
|
999
|
-
upgradedValues.push('');
|
|
1000
|
-
} else {
|
|
1001
|
-
// Column missing, use default value
|
|
1002
|
-
upgradedValues.push(defaultValues[newCol]);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Properly quote values and join
|
|
1007
|
-
return upgradedValues.map((v) => `"${v}"`).join(',');
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
/**
|
|
1011
|
-
* Write workflow manifest CSV
|
|
1012
|
-
* @returns {string} Path to the manifest file
|
|
1013
|
-
*/
|
|
1014
|
-
async writeWorkflowManifest(cfgDir) {
|
|
1015
|
-
const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
|
|
1016
|
-
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
|
1017
|
-
|
|
1018
|
-
// Create CSV header - standalone column removed, canonicalId added as optional column
|
|
1019
|
-
let csv = 'name,description,module,path,canonicalId\n';
|
|
1020
|
-
|
|
1021
|
-
// Build workflows map from discovered workflows only
|
|
1022
|
-
// Old entries are NOT preserved - the manifest reflects what actually exists on disk
|
|
1023
|
-
const allWorkflows = new Map();
|
|
1024
|
-
|
|
1025
|
-
// Only add workflows that were actually discovered in this scan
|
|
1026
|
-
for (const workflow of this.workflows) {
|
|
1027
|
-
const key = `${workflow.module}:${workflow.name}`;
|
|
1028
|
-
allWorkflows.set(key, {
|
|
1029
|
-
name: workflow.name,
|
|
1030
|
-
description: workflow.description,
|
|
1031
|
-
module: workflow.module,
|
|
1032
|
-
path: workflow.path,
|
|
1033
|
-
canonicalId: workflow.canonicalId || '',
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// Write all workflows
|
|
1038
|
-
for (const [, value] of allWorkflows) {
|
|
1039
|
-
const row = [
|
|
1040
|
-
escapeCsv(value.name),
|
|
1041
|
-
escapeCsv(value.description),
|
|
1042
|
-
escapeCsv(value.module),
|
|
1043
|
-
escapeCsv(value.path),
|
|
1044
|
-
escapeCsv(value.canonicalId),
|
|
1045
|
-
].join(',');
|
|
1046
|
-
csv += row + '\n';
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
await fs.writeFile(csvPath, csv);
|
|
1050
|
-
return csvPath;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
508
|
/**
|
|
1054
509
|
* Write skill manifest CSV
|
|
1055
510
|
* @returns {string} Path to the manifest file
|
|
@@ -1150,134 +605,6 @@ class ManifestGenerator {
|
|
|
1150
605
|
return csvPath;
|
|
1151
606
|
}
|
|
1152
607
|
|
|
1153
|
-
/**
|
|
1154
|
-
* Write task manifest CSV
|
|
1155
|
-
* @returns {string} Path to the manifest file
|
|
1156
|
-
*/
|
|
1157
|
-
async writeTaskManifest(cfgDir) {
|
|
1158
|
-
const csvPath = path.join(cfgDir, 'task-manifest.csv');
|
|
1159
|
-
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
|
1160
|
-
|
|
1161
|
-
// Read existing manifest to preserve entries
|
|
1162
|
-
const existingEntries = new Map();
|
|
1163
|
-
if (await fs.pathExists(csvPath)) {
|
|
1164
|
-
const content = await fs.readFile(csvPath, 'utf8');
|
|
1165
|
-
const records = csv.parse(content, {
|
|
1166
|
-
columns: true,
|
|
1167
|
-
skip_empty_lines: true,
|
|
1168
|
-
});
|
|
1169
|
-
for (const record of records) {
|
|
1170
|
-
existingEntries.set(`${record.module}:${record.name}`, record);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Create CSV header with standalone and canonicalId columns
|
|
1175
|
-
let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
|
|
1176
|
-
|
|
1177
|
-
// Combine existing and new tasks
|
|
1178
|
-
const allTasks = new Map();
|
|
1179
|
-
|
|
1180
|
-
// Add existing entries
|
|
1181
|
-
for (const [key, value] of existingEntries) {
|
|
1182
|
-
allTasks.set(key, value);
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
// Add/update new tasks
|
|
1186
|
-
for (const task of this.tasks) {
|
|
1187
|
-
const key = `${task.module}:${task.name}`;
|
|
1188
|
-
allTasks.set(key, {
|
|
1189
|
-
name: task.name,
|
|
1190
|
-
displayName: task.displayName,
|
|
1191
|
-
description: task.description,
|
|
1192
|
-
module: task.module,
|
|
1193
|
-
path: task.path,
|
|
1194
|
-
standalone: task.standalone,
|
|
1195
|
-
canonicalId: task.canonicalId || '',
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Write all tasks
|
|
1200
|
-
for (const [, record] of allTasks) {
|
|
1201
|
-
const row = [
|
|
1202
|
-
escapeCsv(record.name),
|
|
1203
|
-
escapeCsv(record.displayName),
|
|
1204
|
-
escapeCsv(record.description),
|
|
1205
|
-
escapeCsv(record.module),
|
|
1206
|
-
escapeCsv(record.path),
|
|
1207
|
-
escapeCsv(record.standalone),
|
|
1208
|
-
escapeCsv(record.canonicalId),
|
|
1209
|
-
].join(',');
|
|
1210
|
-
csvContent += row + '\n';
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
await fs.writeFile(csvPath, csvContent);
|
|
1214
|
-
return csvPath;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
/**
|
|
1218
|
-
* Write tool manifest CSV
|
|
1219
|
-
* @returns {string} Path to the manifest file
|
|
1220
|
-
*/
|
|
1221
|
-
async writeToolManifest(cfgDir) {
|
|
1222
|
-
const csvPath = path.join(cfgDir, 'tool-manifest.csv');
|
|
1223
|
-
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
|
1224
|
-
|
|
1225
|
-
// Read existing manifest to preserve entries
|
|
1226
|
-
const existingEntries = new Map();
|
|
1227
|
-
if (await fs.pathExists(csvPath)) {
|
|
1228
|
-
const content = await fs.readFile(csvPath, 'utf8');
|
|
1229
|
-
const records = csv.parse(content, {
|
|
1230
|
-
columns: true,
|
|
1231
|
-
skip_empty_lines: true,
|
|
1232
|
-
});
|
|
1233
|
-
for (const record of records) {
|
|
1234
|
-
existingEntries.set(`${record.module}:${record.name}`, record);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// Create CSV header with standalone and canonicalId columns
|
|
1239
|
-
let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
|
|
1240
|
-
|
|
1241
|
-
// Combine existing and new tools
|
|
1242
|
-
const allTools = new Map();
|
|
1243
|
-
|
|
1244
|
-
// Add existing entries
|
|
1245
|
-
for (const [key, value] of existingEntries) {
|
|
1246
|
-
allTools.set(key, value);
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// Add/update new tools
|
|
1250
|
-
for (const tool of this.tools) {
|
|
1251
|
-
const key = `${tool.module}:${tool.name}`;
|
|
1252
|
-
allTools.set(key, {
|
|
1253
|
-
name: tool.name,
|
|
1254
|
-
displayName: tool.displayName,
|
|
1255
|
-
description: tool.description,
|
|
1256
|
-
module: tool.module,
|
|
1257
|
-
path: tool.path,
|
|
1258
|
-
standalone: tool.standalone,
|
|
1259
|
-
canonicalId: tool.canonicalId || '',
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
// Write all tools
|
|
1264
|
-
for (const [, record] of allTools) {
|
|
1265
|
-
const row = [
|
|
1266
|
-
escapeCsv(record.name),
|
|
1267
|
-
escapeCsv(record.displayName),
|
|
1268
|
-
escapeCsv(record.description),
|
|
1269
|
-
escapeCsv(record.module),
|
|
1270
|
-
escapeCsv(record.path),
|
|
1271
|
-
escapeCsv(record.standalone),
|
|
1272
|
-
escapeCsv(record.canonicalId),
|
|
1273
|
-
].join(',');
|
|
1274
|
-
csvContent += row + '\n';
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
await fs.writeFile(csvPath, csvContent);
|
|
1278
|
-
return csvPath;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
608
|
/**
|
|
1282
609
|
* Write files manifest CSV
|
|
1283
610
|
*/
|
|
@@ -1384,11 +711,10 @@ class ManifestGenerator {
|
|
|
1384
711
|
const hasTasks = await fs.pathExists(path.join(modulePath, 'tasks'));
|
|
1385
712
|
const hasTools = await fs.pathExists(path.join(modulePath, 'tools'));
|
|
1386
713
|
|
|
1387
|
-
// Check for native-entrypoint-only modules: recursive scan for
|
|
1388
|
-
// bmad-skill-manifest.yaml with type: skill or type: agent
|
|
714
|
+
// Check for native-entrypoint-only modules: recursive scan for SKILL.md
|
|
1389
715
|
let hasSkills = false;
|
|
1390
716
|
if (!hasAgents && !hasWorkflows && !hasTasks && !hasTools) {
|
|
1391
|
-
hasSkills = await this.
|
|
717
|
+
hasSkills = await this._hasSkillMdRecursive(modulePath);
|
|
1392
718
|
}
|
|
1393
719
|
|
|
1394
720
|
// If it has any of these directories or skill manifests, it's likely a module
|
|
@@ -1404,13 +730,12 @@ class ManifestGenerator {
|
|
|
1404
730
|
}
|
|
1405
731
|
|
|
1406
732
|
/**
|
|
1407
|
-
* Recursively check if a directory tree contains a
|
|
1408
|
-
* declares a native SKILL.md entrypoint (type: skill or type: agent).
|
|
733
|
+
* Recursively check if a directory tree contains a SKILL.md file.
|
|
1409
734
|
* Skips directories starting with . or _.
|
|
1410
735
|
* @param {string} dir - Directory to search
|
|
1411
|
-
* @returns {boolean} True if a
|
|
736
|
+
* @returns {boolean} True if a SKILL.md is found
|
|
1412
737
|
*/
|
|
1413
|
-
async
|
|
738
|
+
async _hasSkillMdRecursive(dir) {
|
|
1414
739
|
let entries;
|
|
1415
740
|
try {
|
|
1416
741
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -1418,15 +743,14 @@ class ManifestGenerator {
|
|
|
1418
743
|
return false;
|
|
1419
744
|
}
|
|
1420
745
|
|
|
1421
|
-
// Check for
|
|
1422
|
-
|
|
1423
|
-
if (this.hasNativeSkillManifest(manifest)) return true;
|
|
746
|
+
// Check for SKILL.md in this directory
|
|
747
|
+
if (entries.some((e) => !e.isDirectory() && e.name === 'SKILL.md')) return true;
|
|
1424
748
|
|
|
1425
749
|
// Recurse into subdirectories
|
|
1426
750
|
for (const entry of entries) {
|
|
1427
751
|
if (!entry.isDirectory()) continue;
|
|
1428
752
|
if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
|
|
1429
|
-
if (await this.
|
|
753
|
+
if (await this._hasSkillMdRecursive(path.join(dir, entry.name))) return true;
|
|
1430
754
|
}
|
|
1431
755
|
|
|
1432
756
|
return false;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/bmad-skill-manifest.yaml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
2
|
-
module: core
|
|
3
|
-
capabilities:
|
|
4
|
-
- name: bmad-distillator
|
|
5
|
-
menu-code: DSTL
|
|
6
|
-
description: "Produces lossless LLM-optimized distillate from source documents. Use after producing large human presentable documents that will be consumed later by LLMs"
|
|
7
|
-
supports-headless: true
|
|
8
|
-
input: source documents
|
|
9
|
-
args: output, validate
|
|
10
|
-
output: single distillate or folder of distillates next to source input
|
|
11
|
-
config-vars-used: null
|
|
12
|
-
phase: anytime
|
|
13
|
-
before: []
|
|
14
|
-
after: []
|
|
15
|
-
is-required: false
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
type: skill
|