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.
- package/CHANGELOG.md +60 -0
- package/README.md +14 -13
- package/_bmad/bme/_artifacts/config.yaml +15 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
- package/_bmad/bme/_gyre/guides/GYRE-TEAM-GUIDE.md +506 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
- package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
- package/_bmad/bme/_team-factory/config.yaml +13 -0
- package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
- package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
- package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
- package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
- package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
- package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
- package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
- package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
- package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
- package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
- package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
- package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
- package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
- package/_bmad/bme/_team-factory/module-help.csv +3 -0
- package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
- package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
- package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
- package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
- package/_bmad/bme/_vortex/config.yaml +4 -4
- package/_bmad/bme/_vortex/guides/VORTEX-TEAM-GUIDE.md +441 -0
- package/package.json +17 -8
- package/scripts/archive.js +26 -45
- package/scripts/convoke-check.js +88 -0
- package/scripts/convoke-doctor.js +303 -4
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +2182 -0
- package/scripts/lib/portfolio/formatters/markdown-formatter.js +40 -0
- package/scripts/lib/portfolio/formatters/terminal-formatter.js +56 -0
- package/scripts/lib/portfolio/portfolio-engine.js +572 -0
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +156 -0
- package/scripts/lib/portfolio/rules/conflict-resolver.js +99 -0
- package/scripts/lib/portfolio/rules/frontmatter-rule.js +42 -0
- package/scripts/lib/portfolio/rules/git-recency-rule.js +69 -0
- package/scripts/lib/types.js +122 -0
- package/scripts/migrate-artifacts.js +439 -0
- package/scripts/portability/catalog-generator.js +353 -0
- package/scripts/portability/classify-skills.js +646 -0
- package/scripts/portability/convoke-export.js +522 -0
- package/scripts/portability/export-engine.js +1133 -0
- package/scripts/portability/generate-adapters.js +79 -0
- package/scripts/portability/manifest-csv.js +147 -0
- package/scripts/portability/seed-catalog-repo.js +427 -0
- package/scripts/portability/templates/canonical-example.md +102 -0
- package/scripts/portability/templates/canonical-format.md +218 -0
- package/scripts/portability/templates/readme-template.md +72 -0
- package/scripts/portability/test-constants.js +42 -0
- package/scripts/portability/validate-classification.js +529 -0
- package/scripts/portability/validate-exports.js +348 -0
- package/scripts/update/lib/agent-registry.js +35 -0
- package/scripts/update/lib/config-merger.js +140 -10
- package/scripts/update/lib/migration-runner.js +1 -1
- package/scripts/update/lib/refresh-installation.js +293 -8
- package/scripts/update/lib/taxonomy-merger.js +138 -0
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
- package/scripts/update/migrations/2.0.x-to-3.1.0.js +50 -0
- package/scripts/update/migrations/3.0.x-to-3.1.0.js +41 -0
- 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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|