convoke-agents 3.0.4 → 3.2.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +14 -13
  3. package/_bmad/bme/_artifacts/config.yaml +15 -0
  4. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
  5. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
  6. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
  7. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
  8. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
  9. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
  10. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
  11. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
  12. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
  13. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
  14. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
  15. package/_bmad/bme/_gyre/guides/GYRE-TEAM-GUIDE.md +506 -0
  16. package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
  17. package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
  18. package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
  19. package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
  20. package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
  21. package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
  22. package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
  23. package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
  24. package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
  25. package/_bmad/bme/_team-factory/config.yaml +13 -0
  26. package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
  27. package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
  28. package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
  29. package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
  30. package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
  31. package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
  32. package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
  33. package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
  34. package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
  35. package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
  36. package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
  37. package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
  38. package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
  39. package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
  40. package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
  41. package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
  42. package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
  43. package/_bmad/bme/_team-factory/module-help.csv +3 -0
  44. package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
  45. package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
  46. package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
  47. package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
  48. package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
  49. package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
  50. package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
  51. package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
  52. package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
  53. package/_bmad/bme/_vortex/config.yaml +4 -4
  54. package/_bmad/bme/_vortex/guides/VORTEX-TEAM-GUIDE.md +441 -0
  55. package/package.json +17 -8
  56. package/scripts/archive.js +26 -45
  57. package/scripts/convoke-check.js +88 -0
  58. package/scripts/convoke-doctor.js +303 -4
  59. package/scripts/install-gyre-agents.js +0 -0
  60. package/scripts/lib/artifact-utils.js +2182 -0
  61. package/scripts/lib/portfolio/formatters/markdown-formatter.js +40 -0
  62. package/scripts/lib/portfolio/formatters/terminal-formatter.js +56 -0
  63. package/scripts/lib/portfolio/portfolio-engine.js +572 -0
  64. package/scripts/lib/portfolio/rules/artifact-chain-rule.js +156 -0
  65. package/scripts/lib/portfolio/rules/conflict-resolver.js +99 -0
  66. package/scripts/lib/portfolio/rules/frontmatter-rule.js +42 -0
  67. package/scripts/lib/portfolio/rules/git-recency-rule.js +69 -0
  68. package/scripts/lib/types.js +122 -0
  69. package/scripts/migrate-artifacts.js +439 -0
  70. package/scripts/portability/catalog-generator.js +353 -0
  71. package/scripts/portability/classify-skills.js +646 -0
  72. package/scripts/portability/convoke-export.js +522 -0
  73. package/scripts/portability/export-engine.js +1133 -0
  74. package/scripts/portability/generate-adapters.js +79 -0
  75. package/scripts/portability/manifest-csv.js +147 -0
  76. package/scripts/portability/seed-catalog-repo.js +427 -0
  77. package/scripts/portability/templates/canonical-example.md +102 -0
  78. package/scripts/portability/templates/canonical-format.md +218 -0
  79. package/scripts/portability/templates/readme-template.md +72 -0
  80. package/scripts/portability/test-constants.js +42 -0
  81. package/scripts/portability/validate-classification.js +529 -0
  82. package/scripts/portability/validate-exports.js +348 -0
  83. package/scripts/update/lib/agent-registry.js +35 -0
  84. package/scripts/update/lib/config-merger.js +140 -10
  85. package/scripts/update/lib/migration-runner.js +1 -1
  86. package/scripts/update/lib/refresh-installation.js +293 -8
  87. package/scripts/update/lib/taxonomy-merger.js +138 -0
  88. package/scripts/update/lib/utils.js +27 -1
  89. package/scripts/update/lib/validator.js +114 -4
  90. package/scripts/update/migrations/2.0.x-to-3.1.0.js +50 -0
  91. package/scripts/update/migrations/3.0.x-to-3.1.0.js +41 -0
  92. package/scripts/update/migrations/registry.js +14 -0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Convoke Check — Local CI mirror.
5
+ *
6
+ * Runs the same checks as .github/workflows/ci.yml so failures are caught
7
+ * before push. Intended for use in dev-story step 9 and manual pre-push.
8
+ *
9
+ * Steps (matching CI jobs):
10
+ * 1. Lint (npm run lint)
11
+ * 2. Unit tests (npm test)
12
+ * 3. Integration (npm run test:integration)
13
+ * 4. Jest lib (npx jest tests/lib/)
14
+ * 5. Coverage (npm run test:coverage) [--skip-coverage to omit]
15
+ *
16
+ * @module convoke-check
17
+ */
18
+
19
+ const { execSync } = require('child_process');
20
+ const { findProjectRoot } = require('./update/lib/utils');
21
+
22
+ const STEPS = [
23
+ { name: 'Lint', cmd: 'npm run lint' },
24
+ { name: 'Unit tests', cmd: 'npm test' },
25
+ { name: 'Integration tests', cmd: 'npm run test:integration' },
26
+ { name: 'Jest lib tests', cmd: 'npx jest tests/lib/ --no-coverage' },
27
+ { name: 'Coverage', cmd: 'npm run test:coverage', skippable: true }
28
+ ];
29
+
30
+ function run() {
31
+ const args = process.argv.slice(2);
32
+
33
+ if (args.includes('--help') || args.includes('-h')) {
34
+ console.log(`
35
+ Usage: convoke-check [options]
36
+
37
+ Runs the full CI-equivalent validation locally.
38
+
39
+ Options:
40
+ --skip-coverage Skip the coverage step (faster)
41
+ --help, -h Show this help
42
+ `);
43
+ return;
44
+ }
45
+
46
+ const projectRoot = findProjectRoot();
47
+ if (!projectRoot) {
48
+ console.error('Error: Not in a Convoke project. Could not find _bmad/ directory.');
49
+ process.exit(1);
50
+ }
51
+
52
+ const skipCoverage = args.includes('--skip-coverage');
53
+ const results = [];
54
+ let failed = false;
55
+
56
+ for (const step of STEPS) {
57
+ if (step.skippable && skipCoverage) {
58
+ results.push({ name: step.name, status: 'skipped' });
59
+ continue;
60
+ }
61
+
62
+ console.log(`\n--- ${step.name} ---`);
63
+ try {
64
+ execSync(step.cmd, { cwd: projectRoot, stdio: 'inherit' });
65
+ results.push({ name: step.name, status: 'pass' });
66
+ } catch {
67
+ results.push({ name: step.name, status: 'FAIL' });
68
+ failed = true;
69
+ // Continue to run remaining steps so all failures are visible
70
+ }
71
+ }
72
+
73
+ // Summary
74
+ console.log('\n=== Convoke Check Summary ===');
75
+ for (const r of results) {
76
+ const icon = r.status === 'pass' ? 'PASS' : r.status === 'skipped' ? 'SKIP' : 'FAIL';
77
+ console.log(` [${icon}] ${r.name}`);
78
+ }
79
+
80
+ if (failed) {
81
+ console.log('\nCI check FAILED. Fix the above issues before pushing.');
82
+ process.exit(1);
83
+ } else {
84
+ console.log('\nAll checks passed. Safe to push.');
85
+ }
86
+ }
87
+
88
+ run();
@@ -5,6 +5,11 @@ const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const yaml = require('js-yaml');
7
7
  const { findProjectRoot, getPackageVersion } = require('./update/lib/utils');
8
+ // Note: parseCsvRow is loaded LAZILY inside loadSkillManifest() (ag-7-2 review patch).
9
+ // Top-level require would crash the doctor on installs missing _team-factory/ — exactly
10
+ // the broken-install case the doctor exists to diagnose. The lazy require is wrapped
11
+ // in try/catch so a missing _team-factory degrades to "skip wrapper checks" instead
12
+ // of crashing the entire CLI.
8
13
 
9
14
  /**
10
15
  * convoke-doctor — Diagnose common Convoke installation issues.
@@ -53,6 +58,10 @@ async function main() {
53
58
  });
54
59
 
55
60
  // 3. Per-module checks
61
+ // ag-7-2 (I31): load skill-manifest once for the wrapper-name lookup used by
62
+ // checkModuleSkillWrappers below. Empty Map is safe (falls back to verbatim).
63
+ const manifestMap = loadSkillManifest(projectRoot);
64
+
56
65
  for (const mod of modules) {
57
66
  const configCheck = checkModuleConfig(mod);
58
67
  checks.push(configCheck);
@@ -63,6 +72,13 @@ async function main() {
63
72
  if (Array.isArray(mod.config.workflows) && mod.config.workflows.length > 0) {
64
73
  checks.push(checkModuleWorkflows(mod));
65
74
  }
75
+ // ag-7-2 (I31): verify skill wrappers exist on disk for standalone-skill workflows.
76
+ // checkModuleSkillWrappers returns null if the module has no manifest entries
77
+ // (i.e., no standalone-skill workflows), so we filter those out.
78
+ if (Array.isArray(mod.config.workflows) && mod.config.workflows.length > 0) {
79
+ const wrapperCheck = checkModuleSkillWrappers(mod, projectRoot, manifestMap);
80
+ if (wrapperCheck) checks.push(wrapperCheck);
81
+ }
66
82
  }
67
83
  }
68
84
 
@@ -70,6 +86,7 @@ async function main() {
70
86
  checks.push(await checkOutputDir(projectRoot));
71
87
  checks.push(checkMigrationLock(projectRoot));
72
88
  checks.push(checkVersionConsistency(projectRoot, modules));
89
+ checks.push(...checkTaxonomy(projectRoot));
73
90
 
74
91
  printResults(checks);
75
92
 
@@ -249,6 +266,161 @@ function checkModuleWorkflows(mod) {
249
266
  return { name: label, passed: true, info: `${workflowNames.length} workflows present` };
250
267
  }
251
268
 
269
+ /**
270
+ * Load skill-manifest.csv into a Map keyed by source path → canonicalId.
271
+ * The manifest is the authoritative source for wrapper directory names
272
+ * (canonicalId = the directory name under .claude/skills/).
273
+ *
274
+ * Returns an empty Map and logs a warning on any read/parse error — never throws,
275
+ * never fails the doctor. checkModuleSkillWrappers will fall back to verbatim
276
+ * workflow names for any source path not in the map.
277
+ *
278
+ * Closes I31 (ag-7-2).
279
+ *
280
+ * @param {string} projectRoot - Absolute path to project root
281
+ * @returns {Map<string, string>} sourcePath → canonicalId
282
+ */
283
+ function loadSkillManifest(projectRoot) {
284
+ const manifestPath = path.join(projectRoot, '_bmad/_config/skill-manifest.csv');
285
+ const map = new Map();
286
+
287
+ if (!fs.existsSync(manifestPath)) {
288
+ console.warn(chalk.yellow(` ⚠ skill-manifest.csv not found at ${manifestPath}; skill wrapper checks will be skipped`));
289
+ return map;
290
+ }
291
+
292
+ // Lazy-load parseCsvRow from the optional _team-factory submodule.
293
+ // ag-7-2 review patch (Edge Case Hunter EH#1): if _team-factory/ is missing
294
+ // (e.g., user opted out of the team-factory module), the top-level require
295
+ // would crash the doctor before main() even runs — exactly the broken-install
296
+ // case the doctor exists to diagnose. Lazy + try/catch degrades cleanly.
297
+ let parseCsvRow;
298
+ try {
299
+ ({ parseCsvRow } = require('../_bmad/bme/_team-factory/lib/utils/csv-utils'));
300
+ } catch (_err) {
301
+ console.warn(chalk.yellow(` ⚠ csv-utils unavailable (_team-factory submodule not installed); skill wrapper checks will be skipped`));
302
+ return map;
303
+ }
304
+
305
+ try {
306
+ const content = fs.readFileSync(manifestPath, 'utf8');
307
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
308
+ if (lines.length === 0) return map;
309
+
310
+ // Parse header to find column indices
311
+ const header = parseCsvRow(lines[0]);
312
+ const canonicalIdIdx = header.indexOf('canonicalId');
313
+ const pathIdx = header.indexOf('path');
314
+ if (canonicalIdIdx === -1 || pathIdx === -1) {
315
+ console.warn(chalk.yellow(` ⚠ skill-manifest.csv missing required columns (canonicalId, path); skill wrapper checks will be skipped`));
316
+ return map;
317
+ }
318
+
319
+ // Parse data rows
320
+ for (let i = 1; i < lines.length; i++) {
321
+ const fields = parseCsvRow(lines[i]);
322
+ if (fields.length <= Math.max(canonicalIdIdx, pathIdx)) continue;
323
+ const canonicalId = fields[canonicalIdIdx];
324
+ const sourcePath = fields[pathIdx];
325
+ if (canonicalId && sourcePath) {
326
+ map.set(sourcePath, canonicalId);
327
+ }
328
+ }
329
+ } catch (err) {
330
+ console.warn(chalk.yellow(` ⚠ skill-manifest.csv parse error (${err.message}); skill wrapper checks will be skipped`));
331
+ return new Map();
332
+ }
333
+
334
+ return map;
335
+ }
336
+
337
+ /**
338
+ * Check that every standalone-skill workflow declared in a module's config.yaml
339
+ * has a corresponding skill wrapper at .claude/skills/{canonicalId}/SKILL.md.
340
+ *
341
+ * Manifest-as-opt-in semantics:
342
+ * - The skill-manifest.csv is the OPT-IN marker for "this workflow is a standalone
343
+ * skill that needs a wrapper." Modules whose workflows are NOT in the manifest
344
+ * (Vortex, Gyre, team-factory, ...) generate menu-patch workflows or agent
345
+ * skills, NOT standalone wrappers — those modules are silently skipped here.
346
+ * - Only Enhance + Artifacts workflows are in the manifest today (after ag-7-2
347
+ * Task 0 added the Enhance row).
348
+ * - If a module has SOME workflows in the manifest and SOME not, only the
349
+ * in-manifest ones are checked.
350
+ *
351
+ * Resolution: source path `_bmad/bme/{mod.name}/workflows/{workflowName}/SKILL.md`
352
+ * → manifest lookup → canonicalId is the wrapper directory name.
353
+ *
354
+ * Failures are aggregated into a single result (mirrors validateEnhanceModule
355
+ * pattern from Story 6.6) so the operator sees every missing wrapper at once.
356
+ *
357
+ * Returns null if the module has NO standalone-skill workflows (caller skips
358
+ * pushing null results into the checks array).
359
+ *
360
+ * Closes I31 (ag-7-2).
361
+ *
362
+ * @param {object} mod - Module descriptor from discoverModules()
363
+ * @param {string} projectRoot - Absolute path to project root
364
+ * @param {Map<string, string>} manifestMap - From loadSkillManifest()
365
+ * @returns {object|null} Doctor check result, or null if module has no standalone-skill workflows
366
+ */
367
+ function checkModuleSkillWrappers(mod, projectRoot, manifestMap) {
368
+ const label = `${mod.name} skill wrappers`;
369
+ const workflowNames = mod.config.workflows;
370
+ const failures = [];
371
+ const checked = [];
372
+
373
+ // Build the source-path prefix from mod.dir (the absolute module directory
374
+ // discovered by discoverModules) relative to projectRoot. ag-7-2 review patch
375
+ // (Blind Hunter BH#1): the previous hardcoded `_bmad/bme/` prefix was correct
376
+ // for current callers (discoverModules only scans bme submodules) but would
377
+ // silently miss if a future caller scans a different team directory.
378
+ const moduleRelPath = path.relative(projectRoot, mod.dir);
379
+
380
+ for (const w of workflowNames) {
381
+ // Null/empty/object-without-name guard. ag-7-2 review patch (Edge Case
382
+ // Hunter EH#4 / Blind Hunter BH#7): a malformed config.yaml with
383
+ // `workflows: [null]` or `workflows: [{}]` would otherwise crash the doctor
384
+ // with a TypeError instead of producing a clean diagnostic.
385
+ if (!w) continue;
386
+ const wfName = typeof w === 'object' ? w.name : w;
387
+ if (!wfName || typeof wfName !== 'string') continue;
388
+
389
+ const sourcePath = `${moduleRelPath}/workflows/${wfName}/SKILL.md`;
390
+
391
+ // Manifest is opt-in: only workflows declared in skill-manifest.csv are
392
+ // standalone-skill workflows that need wrappers. Skip the rest silently.
393
+ const wrapperName = manifestMap.get(sourcePath);
394
+ if (!wrapperName) continue;
395
+
396
+ checked.push(wfName);
397
+ const wrapperPath = path.join(projectRoot, '.claude', 'skills', wrapperName, 'SKILL.md');
398
+ if (!fs.existsSync(wrapperPath)) {
399
+ failures.push(`Missing skill wrapper for ${wfName}: .claude/skills/${wrapperName}/SKILL.md`);
400
+ }
401
+ }
402
+
403
+ // If no workflows in this module are standalone skills, skip the check entirely.
404
+ if (checked.length === 0) {
405
+ return null;
406
+ }
407
+
408
+ if (failures.length > 0) {
409
+ return {
410
+ name: label,
411
+ passed: false,
412
+ error: failures.join('; '),
413
+ fix: 'Run convoke-update to regenerate skill wrappers'
414
+ };
415
+ }
416
+
417
+ return {
418
+ name: label,
419
+ passed: true,
420
+ info: `${checked.length} standalone-skill workflows have wrappers`
421
+ };
422
+ }
423
+
252
424
  async function checkOutputDir(projectRoot) {
253
425
  const outputDir = path.join(projectRoot, '_bmad-output');
254
426
 
@@ -367,7 +539,134 @@ function printResults(checks) {
367
539
  console.log('');
368
540
  }
369
541
 
370
- main().catch(err => {
371
- console.error(chalk.red(`Doctor failed: ${err.message}`));
372
- process.exit(1);
373
- });
542
+ // --- Taxonomy Validation ---
543
+
544
+ /** Valid ID pattern: lowercase alphanumeric with optional dashes */
545
+ const TAXONOMY_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
546
+
547
+ /**
548
+ * Validate taxonomy configuration file.
549
+ * Returns array of check results (never throws).
550
+ * @param {string} projectRoot
551
+ * @returns {Array<{name: string, passed: boolean, error?: string, warning?: string, fix?: string, info?: string}>}
552
+ */
553
+ function checkTaxonomy(projectRoot) {
554
+ const results = [];
555
+ const configPath = path.join(projectRoot, '_bmad', '_config', 'taxonomy.yaml');
556
+
557
+ // Check 1: file exists
558
+ if (!fs.existsSync(configPath)) {
559
+ results.push({
560
+ name: 'Taxonomy: file exists',
561
+ passed: false,
562
+ warning: 'taxonomy.yaml not found at _bmad/_config/taxonomy.yaml',
563
+ fix: 'Run convoke-migrate-artifacts or convoke-update to create it'
564
+ });
565
+ return results;
566
+ }
567
+ results.push({ name: 'Taxonomy: file exists', passed: true });
568
+
569
+ // Check 2: YAML parseable
570
+ let config;
571
+ try {
572
+ config = yaml.load(fs.readFileSync(configPath, 'utf8'));
573
+ } catch (err) {
574
+ results.push({
575
+ name: 'Taxonomy: valid YAML',
576
+ passed: false,
577
+ error: `Invalid YAML in taxonomy.yaml: ${err.message}`,
578
+ fix: 'Fix the YAML syntax in _bmad/_config/taxonomy.yaml'
579
+ });
580
+ return results;
581
+ }
582
+ results.push({ name: 'Taxonomy: valid YAML', passed: true });
583
+
584
+ if (!config || typeof config !== 'object') {
585
+ results.push({ name: 'Taxonomy: structure', passed: false, error: 'taxonomy.yaml is empty or not an object' });
586
+ return results;
587
+ }
588
+
589
+ // Check 3: required sections
590
+ const issues = [];
591
+ if (!config.initiatives || !Array.isArray(config.initiatives.platform)) {
592
+ issues.push('Missing initiatives.platform (must be an array)');
593
+ }
594
+ if (!config.initiatives || !Array.isArray(config.initiatives.user)) {
595
+ issues.push('Missing initiatives.user (must be an array)');
596
+ }
597
+ if (!Array.isArray(config.artifact_types)) {
598
+ issues.push('Missing artifact_types (must be an array)');
599
+ }
600
+
601
+ if (issues.length > 0) {
602
+ results.push({ name: 'Taxonomy: structure', passed: false, error: issues.join('; ') });
603
+ return results;
604
+ }
605
+ results.push({ name: 'Taxonomy: structure', passed: true });
606
+
607
+ // Check 4: ID format validation
608
+ const allPlatform = config.initiatives.platform || [];
609
+ const allUser = config.initiatives.user || [];
610
+ const allTypes = config.artifact_types || [];
611
+ const invalidIds = [];
612
+
613
+ for (const id of [...allPlatform, ...allUser]) {
614
+ if (!TAXONOMY_ID_PATTERN.test(id)) {
615
+ invalidIds.push(`initiative "${id}"`);
616
+ }
617
+ }
618
+ for (const id of allTypes) {
619
+ if (!TAXONOMY_ID_PATTERN.test(id)) {
620
+ invalidIds.push(`artifact_type "${id}"`);
621
+ }
622
+ }
623
+
624
+ if (invalidIds.length > 0) {
625
+ results.push({
626
+ name: 'Taxonomy: ID format',
627
+ passed: false,
628
+ error: `Invalid IDs (must be lowercase alphanumeric with dashes): ${invalidIds.join(', ')}`
629
+ });
630
+ } else {
631
+ results.push({ name: 'Taxonomy: ID format', passed: true });
632
+ }
633
+
634
+ // Check 5: duplicates between platform and user
635
+ const platformSet = new Set(allPlatform);
636
+ const duplicates = allUser.filter(id => platformSet.has(id));
637
+ if (duplicates.length > 0) {
638
+ results.push({
639
+ name: 'Taxonomy: no duplicates',
640
+ passed: false,
641
+ error: `Duplicate IDs in both platform and user sections: ${duplicates.join(', ')}`,
642
+ fix: 'Remove duplicates from the user section (they are already in platform)'
643
+ });
644
+ } else {
645
+ results.push({ name: 'Taxonomy: no duplicates', passed: true });
646
+ }
647
+
648
+ // Check 6: collisions between initiatives and artifact types
649
+ const allInitiatives = new Set([...allPlatform, ...allUser]);
650
+ const collisions = allTypes.filter(t => allInitiatives.has(t));
651
+ if (collisions.length > 0) {
652
+ results.push({
653
+ name: 'Taxonomy: no collisions',
654
+ passed: false,
655
+ error: `IDs used as both initiative and artifact type: ${collisions.join(', ')}`,
656
+ fix: 'Rename the colliding IDs to be unique across sections'
657
+ });
658
+ } else {
659
+ results.push({ name: 'Taxonomy: no collisions', passed: true });
660
+ }
661
+
662
+ return results;
663
+ }
664
+
665
+ if (require.main === module) {
666
+ main().catch(err => {
667
+ console.error(chalk.red(`Doctor failed: ${err.message}`));
668
+ process.exit(1);
669
+ });
670
+ }
671
+
672
+ module.exports = { checkTaxonomy, loadSkillManifest, checkModuleSkillWrappers };
File without changes