convoke-agents 3.0.3 → 3.1.0

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.
@@ -57,8 +57,12 @@ async function main() {
57
57
  const configCheck = checkModuleConfig(mod);
58
58
  checks.push(configCheck);
59
59
  if (configCheck.passed) {
60
- checks.push(checkModuleAgents(mod));
61
- checks.push(checkModuleWorkflows(mod));
60
+ if (Array.isArray(mod.config.agents) && mod.config.agents.length > 0) {
61
+ checks.push(checkModuleAgents(mod));
62
+ }
63
+ if (Array.isArray(mod.config.workflows) && mod.config.workflows.length > 0) {
64
+ checks.push(checkModuleWorkflows(mod));
65
+ }
62
66
  }
63
67
  }
64
68
 
@@ -66,6 +70,7 @@ async function main() {
66
70
  checks.push(await checkOutputDir(projectRoot));
67
71
  checks.push(checkMigrationLock(projectRoot));
68
72
  checks.push(checkVersionConsistency(projectRoot, modules));
73
+ checks.push(...checkTaxonomy(projectRoot));
69
74
 
70
75
  printResults(checks);
71
76
 
@@ -363,7 +368,134 @@ function printResults(checks) {
363
368
  console.log('');
364
369
  }
365
370
 
366
- main().catch(err => {
367
- console.error(chalk.red(`Doctor failed: ${err.message}`));
368
- process.exit(1);
369
- });
371
+ // --- Taxonomy Validation ---
372
+
373
+ /** Valid ID pattern: lowercase alphanumeric with optional dashes */
374
+ const TAXONOMY_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
375
+
376
+ /**
377
+ * Validate taxonomy configuration file.
378
+ * Returns array of check results (never throws).
379
+ * @param {string} projectRoot
380
+ * @returns {Array<{name: string, passed: boolean, error?: string, warning?: string, fix?: string, info?: string}>}
381
+ */
382
+ function checkTaxonomy(projectRoot) {
383
+ const results = [];
384
+ const configPath = path.join(projectRoot, '_bmad', '_config', 'taxonomy.yaml');
385
+
386
+ // Check 1: file exists
387
+ if (!fs.existsSync(configPath)) {
388
+ results.push({
389
+ name: 'Taxonomy: file exists',
390
+ passed: false,
391
+ warning: 'taxonomy.yaml not found at _bmad/_config/taxonomy.yaml',
392
+ fix: 'Run convoke-migrate-artifacts or convoke-update to create it'
393
+ });
394
+ return results;
395
+ }
396
+ results.push({ name: 'Taxonomy: file exists', passed: true });
397
+
398
+ // Check 2: YAML parseable
399
+ let config;
400
+ try {
401
+ config = yaml.load(fs.readFileSync(configPath, 'utf8'));
402
+ } catch (err) {
403
+ results.push({
404
+ name: 'Taxonomy: valid YAML',
405
+ passed: false,
406
+ error: `Invalid YAML in taxonomy.yaml: ${err.message}`,
407
+ fix: 'Fix the YAML syntax in _bmad/_config/taxonomy.yaml'
408
+ });
409
+ return results;
410
+ }
411
+ results.push({ name: 'Taxonomy: valid YAML', passed: true });
412
+
413
+ if (!config || typeof config !== 'object') {
414
+ results.push({ name: 'Taxonomy: structure', passed: false, error: 'taxonomy.yaml is empty or not an object' });
415
+ return results;
416
+ }
417
+
418
+ // Check 3: required sections
419
+ const issues = [];
420
+ if (!config.initiatives || !Array.isArray(config.initiatives.platform)) {
421
+ issues.push('Missing initiatives.platform (must be an array)');
422
+ }
423
+ if (!config.initiatives || !Array.isArray(config.initiatives.user)) {
424
+ issues.push('Missing initiatives.user (must be an array)');
425
+ }
426
+ if (!Array.isArray(config.artifact_types)) {
427
+ issues.push('Missing artifact_types (must be an array)');
428
+ }
429
+
430
+ if (issues.length > 0) {
431
+ results.push({ name: 'Taxonomy: structure', passed: false, error: issues.join('; ') });
432
+ return results;
433
+ }
434
+ results.push({ name: 'Taxonomy: structure', passed: true });
435
+
436
+ // Check 4: ID format validation
437
+ const allPlatform = config.initiatives.platform || [];
438
+ const allUser = config.initiatives.user || [];
439
+ const allTypes = config.artifact_types || [];
440
+ const invalidIds = [];
441
+
442
+ for (const id of [...allPlatform, ...allUser]) {
443
+ if (!TAXONOMY_ID_PATTERN.test(id)) {
444
+ invalidIds.push(`initiative "${id}"`);
445
+ }
446
+ }
447
+ for (const id of allTypes) {
448
+ if (!TAXONOMY_ID_PATTERN.test(id)) {
449
+ invalidIds.push(`artifact_type "${id}"`);
450
+ }
451
+ }
452
+
453
+ if (invalidIds.length > 0) {
454
+ results.push({
455
+ name: 'Taxonomy: ID format',
456
+ passed: false,
457
+ error: `Invalid IDs (must be lowercase alphanumeric with dashes): ${invalidIds.join(', ')}`
458
+ });
459
+ } else {
460
+ results.push({ name: 'Taxonomy: ID format', passed: true });
461
+ }
462
+
463
+ // Check 5: duplicates between platform and user
464
+ const platformSet = new Set(allPlatform);
465
+ const duplicates = allUser.filter(id => platformSet.has(id));
466
+ if (duplicates.length > 0) {
467
+ results.push({
468
+ name: 'Taxonomy: no duplicates',
469
+ passed: false,
470
+ error: `Duplicate IDs in both platform and user sections: ${duplicates.join(', ')}`,
471
+ fix: 'Remove duplicates from the user section (they are already in platform)'
472
+ });
473
+ } else {
474
+ results.push({ name: 'Taxonomy: no duplicates', passed: true });
475
+ }
476
+
477
+ // Check 6: collisions between initiatives and artifact types
478
+ const allInitiatives = new Set([...allPlatform, ...allUser]);
479
+ const collisions = allTypes.filter(t => allInitiatives.has(t));
480
+ if (collisions.length > 0) {
481
+ results.push({
482
+ name: 'Taxonomy: no collisions',
483
+ passed: false,
484
+ error: `IDs used as both initiative and artifact type: ${collisions.join(', ')}`,
485
+ fix: 'Rename the colliding IDs to be unique across sections'
486
+ });
487
+ } else {
488
+ results.push({ name: 'Taxonomy: no collisions', passed: true });
489
+ }
490
+
491
+ return results;
492
+ }
493
+
494
+ if (require.main === module) {
495
+ main().catch(err => {
496
+ console.error(chalk.red(`Doctor failed: ${err.message}`));
497
+ process.exit(1);
498
+ });
499
+ }
500
+
501
+ module.exports = { checkTaxonomy };