convoke-agents 3.1.0 → 3.2.1
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 +31 -0
- package/README.md +37 -10
- 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/_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/package.json +13 -7
- package/scripts/convoke-doctor.js +172 -1
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +521 -13
- package/scripts/lib/portfolio/portfolio-engine.js +301 -34
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +33 -3
- package/scripts/lib/portfolio/rules/conflict-resolver.js +22 -0
- package/scripts/migrate-artifacts.js +69 -10
- 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 +1156 -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/refresh-installation.js +293 -8
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const yaml = require('js-yaml');
|
|
6
|
-
const
|
|
6
|
+
const YAML = require('yaml'); // Comment-preserving YAML library (ag-7-1: I29). Use for WRITE sites that need to preserve comments. js-yaml stays for read-only consumers.
|
|
7
|
+
const { getPackageVersion, assertVersion } = require('./utils');
|
|
7
8
|
const configMerger = require('./config-merger');
|
|
8
|
-
const { AGENTS, AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, USER_GUIDES, GYRE_AGENTS, GYRE_AGENT_FILES, GYRE_AGENT_IDS, GYRE_WORKFLOW_NAMES } = require('./agent-registry');
|
|
9
|
+
const { AGENTS, AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, USER_GUIDES, GYRE_AGENTS, GYRE_AGENT_FILES, GYRE_AGENT_IDS, GYRE_WORKFLOW_NAMES, EXTRA_BME_AGENTS } = require('./agent-registry');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Refresh Installation for Convoke
|
|
@@ -91,6 +92,34 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
91
92
|
if (verbose) console.log(' Skipped workflow copy (dev environment)');
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// 2b1. Standalone bme submodule trees (e.g., _team-factory)
|
|
96
|
+
// Each EXTRA_BME_AGENTS entry references a submodule directory under _bmad/bme/
|
|
97
|
+
// that must be copied wholesale so the agent file, workflows, lib code, and config travel together.
|
|
98
|
+
// Mirrors the workflow loop pattern (2-step remove-then-copy) so renamed/deleted files
|
|
99
|
+
// in the package don't survive in the user install as stale leftovers.
|
|
100
|
+
const copiedExtraSubmodules = new Set();
|
|
101
|
+
if (!isSameRoot) {
|
|
102
|
+
for (const agent of EXTRA_BME_AGENTS) {
|
|
103
|
+
if (copiedExtraSubmodules.has(agent.submodule)) continue;
|
|
104
|
+
copiedExtraSubmodules.add(agent.submodule);
|
|
105
|
+
const srcDir = path.join(packageRoot, '_bmad', 'bme', agent.submodule);
|
|
106
|
+
const destDir = path.join(projectRoot, '_bmad', 'bme', agent.submodule);
|
|
107
|
+
if (fs.existsSync(srcDir)) {
|
|
108
|
+
// Remove existing destination first to clear stale files
|
|
109
|
+
// (e.g., renamed/deleted workflow steps from previous versions)
|
|
110
|
+
if (fs.existsSync(destDir)) {
|
|
111
|
+
await fs.remove(destDir);
|
|
112
|
+
}
|
|
113
|
+
await fs.copy(srcDir, destDir, { overwrite: true });
|
|
114
|
+
changes.push(`Refreshed standalone bme submodule: ${agent.submodule}`);
|
|
115
|
+
if (verbose) console.log(` Refreshed standalone bme submodule: ${agent.submodule}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
changes.push('Skipped standalone bme submodule copy (dev environment — files already in place)');
|
|
120
|
+
if (verbose) console.log(' Skipped standalone bme submodule copy (dev environment)');
|
|
121
|
+
}
|
|
122
|
+
|
|
94
123
|
// 2a. Enhance module — read config, copy directory tree, patch target agent menu
|
|
95
124
|
const packageEnhance = path.join(packageRoot, '_bmad', 'bme', '_enhance');
|
|
96
125
|
const enhanceConfigPath = path.join(packageEnhance, 'config.yaml');
|
|
@@ -115,12 +144,17 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
115
144
|
|
|
116
145
|
if (!isSameRoot) {
|
|
117
146
|
await fs.copy(packageEnhance, targetEnhance, { overwrite: true });
|
|
118
|
-
// Stamp enhance config version to match package version
|
|
147
|
+
// Stamp enhance config version to match package version (ag-7-1: I30 + I29).
|
|
148
|
+
// Uses comment-preserving YAML.parseDocument so the doc comments survive.
|
|
119
149
|
const targetEnhanceConfig = path.join(targetEnhance, 'config.yaml');
|
|
120
150
|
if (fs.existsSync(targetEnhanceConfig)) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
assertVersion(version, 'enhance');
|
|
152
|
+
const ecDoc = YAML.parseDocument(fs.readFileSync(targetEnhanceConfig, 'utf8'));
|
|
153
|
+
if (ecDoc.errors && ecDoc.errors.length > 0) {
|
|
154
|
+
throw new Error(`Refresh: cannot parse Enhance config.yaml: ${ecDoc.errors[0].message}`);
|
|
155
|
+
}
|
|
156
|
+
ecDoc.set('version', version);
|
|
157
|
+
fs.writeFileSync(targetEnhanceConfig, ecDoc.toString({ lineWidth: 0 }), 'utf8');
|
|
124
158
|
}
|
|
125
159
|
changes.push('Refreshed Enhance module: _bmad/bme/_enhance/');
|
|
126
160
|
if (verbose) console.log(' Refreshed Enhance module: _bmad/bme/_enhance/');
|
|
@@ -189,6 +223,61 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
189
223
|
}
|
|
190
224
|
}
|
|
191
225
|
|
|
226
|
+
// 2c. Artifacts module — read config, copy directory tree, generate skill wrappers
|
|
227
|
+
// Workflow-only submodule (no agents). Workflows are STANDALONE: each gets a Claude Code
|
|
228
|
+
// skill wrapper but NO menu patch. The `standalone: true` flag in the workflow entry is
|
|
229
|
+
// the discriminator — workflows without it are NOT supported in this module today (Story 6.6).
|
|
230
|
+
const packageArtifacts = path.join(packageRoot, '_bmad', 'bme', '_artifacts');
|
|
231
|
+
const artifactsConfigPath = path.join(packageArtifacts, 'config.yaml');
|
|
232
|
+
|
|
233
|
+
let artifactsConfig = null;
|
|
234
|
+
if (fs.existsSync(artifactsConfigPath)) {
|
|
235
|
+
try {
|
|
236
|
+
artifactsConfig = yaml.load(fs.readFileSync(artifactsConfigPath, 'utf8'));
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const msg = `Artifacts config.yaml parse error: ${err.message} — skipping Artifacts installation`;
|
|
239
|
+
changes.push(msg);
|
|
240
|
+
if (verbose) console.log(` ⚠ ${msg}`);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
changes.push('Artifacts config.yaml not found — skipping Artifacts installation');
|
|
244
|
+
if (verbose) console.log(' ⚠ Artifacts config.yaml not found — skipping Artifacts installation');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (artifactsConfig) {
|
|
248
|
+
// Copy _artifacts/ directory tree
|
|
249
|
+
const targetArtifacts = path.join(projectRoot, '_bmad', 'bme', '_artifacts');
|
|
250
|
+
|
|
251
|
+
if (!isSameRoot) {
|
|
252
|
+
// Remove existing destination first to clear stale files
|
|
253
|
+
if (fs.existsSync(targetArtifacts)) {
|
|
254
|
+
await fs.remove(targetArtifacts);
|
|
255
|
+
}
|
|
256
|
+
await fs.copy(packageArtifacts, targetArtifacts, { overwrite: true });
|
|
257
|
+
// Stamp artifacts config version to match package version (ag-7-1: I30 + I29).
|
|
258
|
+
// Uses comment-preserving YAML.parseDocument so the standalone:true doc comments survive.
|
|
259
|
+
const targetArtifactsConfig = path.join(targetArtifacts, 'config.yaml');
|
|
260
|
+
if (fs.existsSync(targetArtifactsConfig)) {
|
|
261
|
+
assertVersion(version, 'artifacts');
|
|
262
|
+
const acDoc = YAML.parseDocument(fs.readFileSync(targetArtifactsConfig, 'utf8'));
|
|
263
|
+
if (acDoc.errors && acDoc.errors.length > 0) {
|
|
264
|
+
throw new Error(`Refresh: cannot parse Artifacts config.yaml: ${acDoc.errors[0].message}`);
|
|
265
|
+
}
|
|
266
|
+
acDoc.set('version', version);
|
|
267
|
+
fs.writeFileSync(targetArtifactsConfig, acDoc.toString({ lineWidth: 0 }), 'utf8');
|
|
268
|
+
}
|
|
269
|
+
changes.push('Refreshed Artifacts module: _bmad/bme/_artifacts/');
|
|
270
|
+
if (verbose) console.log(' Refreshed Artifacts module: _bmad/bme/_artifacts/');
|
|
271
|
+
} else {
|
|
272
|
+
changes.push('Skipped Artifacts copy (dev environment — files already in place)');
|
|
273
|
+
if (verbose) console.log(' Skipped Artifacts copy (dev environment)');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Skill wrapper generation for each workflow happens later in section 6d,
|
|
277
|
+
// after skillsDir is defined (mirrors Enhance pattern: config/copy here, skill
|
|
278
|
+
// wrappers in section 6c after agent skills are generated).
|
|
279
|
+
}
|
|
280
|
+
|
|
192
281
|
// 2d. Gyre module — copy agents, workflows, contracts, config
|
|
193
282
|
const packageGyre = path.join(packageRoot, '_bmad', 'bme', '_gyre');
|
|
194
283
|
const targetGyre = path.join(projectRoot, '_bmad', 'bme', '_gyre');
|
|
@@ -257,6 +346,7 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
257
346
|
agents: GYRE_AGENT_IDS,
|
|
258
347
|
workflows: GYRE_WORKFLOW_NAMES
|
|
259
348
|
};
|
|
349
|
+
assertVersion(version, 'config-merger:gyre'); // ag-7-1: defense-in-depth before mergeConfig
|
|
260
350
|
const gyreConfigMerged = await configMerger.mergeConfig(gyreConfigTarget, version, gyreUpdates);
|
|
261
351
|
await configMerger.writeConfig(gyreConfigTarget, gyreConfigMerged);
|
|
262
352
|
changes.push(`Updated Gyre config.yaml to v${version}`);
|
|
@@ -282,6 +372,7 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
282
372
|
workflows: WORKFLOW_NAMES
|
|
283
373
|
};
|
|
284
374
|
|
|
375
|
+
assertVersion(version, 'config-merger:vortex'); // ag-7-1: defense-in-depth before mergeConfig
|
|
285
376
|
const merged = await configMerger.mergeConfig(configPath, version, updates);
|
|
286
377
|
await configMerger.writeConfig(configPath, merged);
|
|
287
378
|
changes.push(`Updated config.yaml to v${version}`);
|
|
@@ -383,16 +474,46 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
383
474
|
].map(csvEscape).join(',');
|
|
384
475
|
}
|
|
385
476
|
|
|
477
|
+
// Row builder for standalone bme agents (e.g., team-factory) — submodule path differs from team agents
|
|
478
|
+
function buildExtraBmeAgentRow610(a) {
|
|
479
|
+
const p = a.persona;
|
|
480
|
+
return [
|
|
481
|
+
csvEscape(a.name),
|
|
482
|
+
csvEscape(''),
|
|
483
|
+
csvEscape(a.title),
|
|
484
|
+
csvEscape(a.icon),
|
|
485
|
+
csvEscape(''),
|
|
486
|
+
csvEscape(p.role),
|
|
487
|
+
csvEscape(p.identity),
|
|
488
|
+
csvEscape(p.communication_style),
|
|
489
|
+
csvEscape(p.expertise),
|
|
490
|
+
csvEscape('bme'),
|
|
491
|
+
csvEscape(`_bmad/bme/${a.submodule}/agents/${a.id}.md`),
|
|
492
|
+
csvEscape(`bmad-agent-bme-${a.id}`),
|
|
493
|
+
].join(',');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function buildExtraBmeAgentRowLegacy(a) {
|
|
497
|
+
const p = a.persona;
|
|
498
|
+
return [
|
|
499
|
+
a.id, a.name, a.title, a.icon,
|
|
500
|
+
p.role, p.identity, p.communication_style, p.expertise,
|
|
501
|
+
'bme', `_bmad/bme/${a.submodule}/agents/${a.id}.md`,
|
|
502
|
+
].map(csvEscape).join(',');
|
|
503
|
+
}
|
|
504
|
+
|
|
386
505
|
let bmeRows;
|
|
387
506
|
if (isV610) {
|
|
388
507
|
bmeRows = [
|
|
389
508
|
...AGENTS.map(a => buildAgentRow610(a, '_vortex')),
|
|
390
509
|
...GYRE_AGENTS.map(a => buildAgentRow610(a, '_gyre')),
|
|
510
|
+
...EXTRA_BME_AGENTS.map(buildExtraBmeAgentRow610),
|
|
391
511
|
];
|
|
392
512
|
} else {
|
|
393
513
|
bmeRows = [
|
|
394
514
|
...AGENTS.map(a => buildAgentRowLegacy(a, '_vortex')),
|
|
395
515
|
...GYRE_AGENTS.map(a => buildAgentRowLegacy(a, '_gyre')),
|
|
516
|
+
...EXTRA_BME_AGENTS.map(buildExtraBmeAgentRowLegacy),
|
|
396
517
|
];
|
|
397
518
|
}
|
|
398
519
|
|
|
@@ -446,6 +567,7 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
446
567
|
const currentSkillDirs = new Set([
|
|
447
568
|
...AGENTS.map(a => `bmad-agent-bme-${a.id}`),
|
|
448
569
|
...GYRE_AGENTS.map(a => `bmad-agent-bme-${a.id}`),
|
|
570
|
+
...EXTRA_BME_AGENTS.map(a => `bmad-agent-bme-${a.id}`),
|
|
449
571
|
]);
|
|
450
572
|
if (fs.existsSync(skillsDir)) {
|
|
451
573
|
const existingSkills = (await fs.readdir(skillsDir)).filter(d => d.startsWith('bmad-agent-bme-'));
|
|
@@ -507,6 +629,31 @@ You must fully embody this agent's persona and follow all activation instruction
|
|
|
507
629
|
if (verbose) console.log(` Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
508
630
|
}
|
|
509
631
|
|
|
632
|
+
// 6b1. Generate .claude/skills/ for standalone bme agents (e.g., team-factory)
|
|
633
|
+
for (const agent of EXTRA_BME_AGENTS) {
|
|
634
|
+
const skillDir = path.join(skillsDir, `bmad-agent-bme-${agent.id}`);
|
|
635
|
+
await fs.ensureDir(skillDir);
|
|
636
|
+
const content = `---
|
|
637
|
+
name: bmad-agent-bme-${agent.id}
|
|
638
|
+
description: ${agent.id} agent
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
642
|
+
|
|
643
|
+
<agent-activation CRITICAL="TRUE">
|
|
644
|
+
1. LOAD the FULL agent file from {project-root}/_bmad/bme/${agent.submodule}/agents/${agent.id}.md
|
|
645
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
646
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
647
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
648
|
+
5. PRESENT the numbered menu
|
|
649
|
+
6. WAIT for user input before proceeding
|
|
650
|
+
</agent-activation>
|
|
651
|
+
`;
|
|
652
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), content, 'utf8');
|
|
653
|
+
changes.push(`Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
654
|
+
if (verbose) console.log(` Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
655
|
+
}
|
|
656
|
+
|
|
510
657
|
// 6c. Copy Enhance workflow skill wrappers and register in manifests
|
|
511
658
|
if (enhanceConfig && !isSameRoot) {
|
|
512
659
|
for (const workflow of enhanceConfig.workflows || []) {
|
|
@@ -540,7 +687,7 @@ You must fully embody this agent's persona and follow all activation instruction
|
|
|
540
687
|
if (fs.existsSync(skManifestPath)) {
|
|
541
688
|
const skCsv = fs.readFileSync(skManifestPath, 'utf8');
|
|
542
689
|
if (!skCsv.includes(`"${canonicalId}"`)) {
|
|
543
|
-
const skRow = `\n"${canonicalId}","${canonicalId}","Manage RICE initiatives backlog — triage review findings, rescore existing items, or bootstrap new backlogs.","bme","_bmad/bme/_enhance/workflows/${workflow.name}/SKILL.md","true"
|
|
690
|
+
const skRow = `\n"${canonicalId}","${canonicalId}","Manage RICE initiatives backlog — triage review findings, rescore existing items, or bootstrap new backlogs.","bme","_bmad/bme/_enhance/workflows/${workflow.name}/SKILL.md","true",,,`;
|
|
544
691
|
fs.appendFileSync(skManifestPath, skRow, 'utf8');
|
|
545
692
|
changes.push(`Added ${canonicalId} to skill-manifest.csv`);
|
|
546
693
|
if (verbose) console.log(` Added ${canonicalId} to skill-manifest.csv`);
|
|
@@ -554,6 +701,80 @@ You must fully embody this agent's persona and follow all activation instruction
|
|
|
554
701
|
if (verbose) console.log(' Skipped Enhance skill registration (dev environment)');
|
|
555
702
|
}
|
|
556
703
|
|
|
704
|
+
// 6d. Copy Artifacts workflow skill wrappers (Story 6.6)
|
|
705
|
+
// Each standalone:true workflow gets a skill wrapper at .claude/skills/{workflow.name}/SKILL.md.
|
|
706
|
+
// workflow.name already carries the bmad- prefix, so we use it verbatim (unlike Enhance which
|
|
707
|
+
// synthesizes bmad-enhance-${workflow.name}). The remove-then-copy pattern clears any leftover
|
|
708
|
+
// files from prior installs (e.g., the obsolete bmad-portfolio-status/workflow.md thin wrapper).
|
|
709
|
+
if (artifactsConfig && !isSameRoot) {
|
|
710
|
+
for (const workflow of artifactsConfig.workflows || []) {
|
|
711
|
+
if (workflow.standalone !== true) {
|
|
712
|
+
const msg = `Artifacts: workflow ${workflow.name} has no standalone:true flag — only standalone workflows are supported, skipping`;
|
|
713
|
+
changes.push(msg);
|
|
714
|
+
if (verbose) console.log(` ⚠ ${msg}`);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const destSkillDir = path.join(skillsDir, workflow.name);
|
|
719
|
+
|
|
720
|
+
// Remove the destination directory first to clear leftover files from prior installs
|
|
721
|
+
if (fs.existsSync(destSkillDir)) {
|
|
722
|
+
await fs.remove(destSkillDir);
|
|
723
|
+
}
|
|
724
|
+
await fs.ensureDir(destSkillDir);
|
|
725
|
+
|
|
726
|
+
// Copy source SKILL.md from the package (the SKILL.md uses an absolute {project-root}
|
|
727
|
+
// path to load workflow.md, so workflow.md does NOT need to be co-located).
|
|
728
|
+
const sourceSkillPath = path.join(packageRoot, '_bmad', 'bme', '_artifacts', 'workflows', workflow.name, 'SKILL.md');
|
|
729
|
+
const targetSkillPath = path.join(destSkillDir, 'SKILL.md');
|
|
730
|
+
if (fs.existsSync(sourceSkillPath)) {
|
|
731
|
+
await fs.copy(sourceSkillPath, targetSkillPath, { overwrite: true });
|
|
732
|
+
changes.push(`Generated skill wrapper: ${workflow.name}`);
|
|
733
|
+
if (verbose) console.log(` Generated skill wrapper: ${workflow.name}`);
|
|
734
|
+
} else {
|
|
735
|
+
const msg = `Artifacts: source SKILL.md not found for ${workflow.name} at ${sourceSkillPath}`;
|
|
736
|
+
changes.push(msg);
|
|
737
|
+
if (verbose) console.log(` ⚠ ${msg}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} else if (artifactsConfig && isSameRoot) {
|
|
741
|
+
changes.push('Skipped Artifacts skill wrapper generation (dev environment — source files unchanged)');
|
|
742
|
+
if (verbose) console.log(' Skipped Artifacts skill wrapper generation (dev environment)');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// 6e. Orphan workflow-wrapper cleanup (Story 7.4, I32)
|
|
746
|
+
// Removes stale .claude/skills/ directories for workflow wrappers that are no longer
|
|
747
|
+
// declared in the module configs. Uses a two-strategy matching approach:
|
|
748
|
+
// Strategy 1 (Enhance): any bmad-enhance-* dir not in the current union → orphan
|
|
749
|
+
// Strategy 2 (Artifacts): any dir whose name exactly matches a known Artifacts
|
|
750
|
+
// workflow name but is not in the current union → orphan
|
|
751
|
+
// All other directories (agent wrappers, upstream BMAD skills, third-party) are ignored.
|
|
752
|
+
if (!isSameRoot) {
|
|
753
|
+
const currentWorkflowWrappers = new Set();
|
|
754
|
+
// Enhance wrappers: bmad-enhance-${workflow.name}
|
|
755
|
+
if (enhanceConfig && Array.isArray(enhanceConfig.workflows)) {
|
|
756
|
+
for (const wf of enhanceConfig.workflows) {
|
|
757
|
+
if (wf && wf.name) currentWorkflowWrappers.add(`bmad-enhance-${wf.name}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Artifacts wrappers: workflow.name verbatim (only standalone:true are installed,
|
|
761
|
+
// but we track ALL names so a removed standalone workflow is still recognized as an orphan)
|
|
762
|
+
const knownArtifactsNames = new Set();
|
|
763
|
+
if (artifactsConfig && Array.isArray(artifactsConfig.workflows)) {
|
|
764
|
+
for (const wf of artifactsConfig.workflows) {
|
|
765
|
+
if (wf && wf.name) {
|
|
766
|
+
knownArtifactsNames.add(wf.name);
|
|
767
|
+
if (wf.standalone === true) currentWorkflowWrappers.add(wf.name);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const orphanChanges = cleanupOrphanWorkflowWrappers(skillsDir, currentWorkflowWrappers, knownArtifactsNames, { verbose });
|
|
772
|
+
changes.push(...orphanChanges);
|
|
773
|
+
} else {
|
|
774
|
+
changes.push('Skipped orphan workflow-wrapper cleanup (dev environment)');
|
|
775
|
+
if (verbose) console.log(' Skipped orphan workflow-wrapper cleanup (dev environment)');
|
|
776
|
+
}
|
|
777
|
+
|
|
557
778
|
// 7. Generate agent customize files (only if they don't already exist)
|
|
558
779
|
const customizeDir = path.join(projectRoot, '_bmad', '_config', 'agents');
|
|
559
780
|
await fs.ensureDir(customizeDir);
|
|
@@ -599,4 +820,68 @@ prompts: []
|
|
|
599
820
|
return changes;
|
|
600
821
|
}
|
|
601
822
|
|
|
602
|
-
|
|
823
|
+
/**
|
|
824
|
+
* Remove orphan workflow-wrapper directories from .claude/skills/.
|
|
825
|
+
*
|
|
826
|
+
* Two-strategy matching (Story 7.4, I32):
|
|
827
|
+
* Strategy 1: Enhance prefix — any dir starting with `bmad-enhance-` that is
|
|
828
|
+
* not in `currentWrappers` is an orphan.
|
|
829
|
+
* Strategy 2: Artifacts exact-name — any dir whose name is in `knownArtifactsNames`
|
|
830
|
+
* but not in `currentWrappers` is an orphan.
|
|
831
|
+
* Everything else (agent wrappers, upstream BMAD skills, third-party) is ignored.
|
|
832
|
+
*
|
|
833
|
+
* @param {string} skillsDir - Absolute path to .claude/skills/
|
|
834
|
+
* @param {Set<string>} currentWrappers - Union of live workflow wrapper names
|
|
835
|
+
* @param {Set<string>} knownArtifactsNames - ALL Artifacts workflow names (including non-standalone)
|
|
836
|
+
* @param {object} [options]
|
|
837
|
+
* @param {boolean} [options.verbose] - Log each action
|
|
838
|
+
* @returns {Array<string>} Changes array entries for removed orphans
|
|
839
|
+
*/
|
|
840
|
+
function cleanupOrphanWorkflowWrappers(skillsDir, currentWrappers, knownArtifactsNames, options = {}) {
|
|
841
|
+
// Deliberately synchronous (fs.removeSync / fs.readdirSync) — the function returns
|
|
842
|
+
// Array<string>, not a Promise. The sync pattern keeps the contract simple for both
|
|
843
|
+
// the caller (section 6e spreads the result into changes[]) and the test file (which
|
|
844
|
+
// imports the function directly without async scaffolding). The existing agent
|
|
845
|
+
// stale-skill sweep at section 6 uses async fs.remove because it runs inline in the
|
|
846
|
+
// async refreshInstallation body; this function is extracted to be testable standalone.
|
|
847
|
+
const { verbose = false } = options;
|
|
848
|
+
const changes = [];
|
|
849
|
+
|
|
850
|
+
if (!fs.existsSync(skillsDir)) return changes;
|
|
851
|
+
|
|
852
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
853
|
+
|
|
854
|
+
for (const entry of entries) {
|
|
855
|
+
if (!entry.isDirectory()) continue;
|
|
856
|
+
const name = entry.name;
|
|
857
|
+
|
|
858
|
+
// Skip agent wrappers (handled by existing stale-skill sweep)
|
|
859
|
+
if (name.startsWith('bmad-agent-bme-')) continue;
|
|
860
|
+
|
|
861
|
+
// Strategy 1: Enhance prefix (unambiguous — no upstream module uses bmad-enhance-)
|
|
862
|
+
if (name.startsWith('bmad-enhance-')) {
|
|
863
|
+
if (!currentWrappers.has(name)) {
|
|
864
|
+
fs.removeSync(path.join(skillsDir, name));
|
|
865
|
+
changes.push(`Removed orphan skill wrapper: ${name}`);
|
|
866
|
+
if (verbose) console.log(` Removed orphan skill wrapper: ${name}`);
|
|
867
|
+
}
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Strategy 2: Artifacts exact-name match
|
|
872
|
+
if (knownArtifactsNames.has(name)) {
|
|
873
|
+
if (!currentWrappers.has(name)) {
|
|
874
|
+
fs.removeSync(path.join(skillsDir, name));
|
|
875
|
+
changes.push(`Removed orphan skill wrapper: ${name}`);
|
|
876
|
+
if (verbose) console.log(` Removed orphan skill wrapper: ${name}`);
|
|
877
|
+
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Everything else: not a workflow wrapper we own — leave alone
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return changes;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
module.exports = { refreshInstallation, cleanupOrphanWorkflowWrappers };
|
|
@@ -88,9 +88,35 @@ function findProjectRoot() {
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Assert that a version string is valid before stamping a config file.
|
|
93
|
+
* Throws a clear error if version is undefined, null, or empty.
|
|
94
|
+
* Used by refresh-installation.js, config-merger.js, and any future config writer
|
|
95
|
+
* that stamps a version field — closes I30 (ag-7-1).
|
|
96
|
+
*
|
|
97
|
+
* @param {string} version - The version string to validate
|
|
98
|
+
* @param {string} callSite - Identifier for the call site (e.g., 'enhance', 'artifacts', 'config-merger')
|
|
99
|
+
* @throws {Error} if version is not a non-empty string
|
|
100
|
+
*/
|
|
101
|
+
function assertVersion(version, callSite) {
|
|
102
|
+
// Reject all non-string types (numeric 0, boolean false, etc.) — version
|
|
103
|
+
// must be a non-empty string. Closes Blind Hunter finding #9 (ag-7-1 review).
|
|
104
|
+
if (typeof version !== 'string' || version === '') {
|
|
105
|
+
let displayed;
|
|
106
|
+
if (version === null) displayed = 'null';
|
|
107
|
+
else if (version === undefined) displayed = 'undefined';
|
|
108
|
+
else if (version === '') displayed = "''";
|
|
109
|
+
else displayed = `${typeof version} (${String(version)})`;
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Refresh: cannot stamp config — getPackageVersion() returned ${displayed}; check package.json (call site: ${callSite})`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
91
116
|
module.exports = {
|
|
92
117
|
getPackageVersion,
|
|
93
118
|
compareVersions,
|
|
94
119
|
countUserDataFiles,
|
|
95
|
-
findProjectRoot
|
|
120
|
+
findProjectRoot,
|
|
121
|
+
assertVersion
|
|
96
122
|
};
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const yaml = require('js-yaml');
|
|
6
6
|
const configMerger = require('./config-merger');
|
|
7
7
|
const { countUserDataFiles } = require('./utils');
|
|
8
|
-
const { AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, WAVE3_WORKFLOW_NAMES } = require('./agent-registry');
|
|
8
|
+
const { AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, WAVE3_WORKFLOW_NAMES, EXTRA_BME_AGENTS, EXTRA_BME_AGENT_IDS } = require('./agent-registry');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Validator for Convoke
|
|
@@ -47,6 +47,9 @@ async function validateInstallation(preMigrationData = {}, projectRoot) {
|
|
|
47
47
|
// 8. Enhance module validation (optional — passes if not installed)
|
|
48
48
|
checks.push(await validateEnhanceModule(projectRoot));
|
|
49
49
|
|
|
50
|
+
// 9. Artifacts module validation (optional — passes if not installed)
|
|
51
|
+
checks.push(await validateArtifactsModule(projectRoot));
|
|
52
|
+
|
|
50
53
|
const allPassed = checks.every(c => c.passed);
|
|
51
54
|
|
|
52
55
|
return {
|
|
@@ -202,11 +205,22 @@ async function validateManifest(projectRoot) {
|
|
|
202
205
|
|
|
203
206
|
const manifestContent = fs.readFileSync(manifestPath, 'utf8');
|
|
204
207
|
|
|
205
|
-
// Check for all Convoke agents
|
|
208
|
+
// Check for all Convoke agents (Vortex/Gyre IDs and standalone bme agents)
|
|
206
209
|
const missingFromManifest = AGENT_IDS.filter(id => !manifestContent.includes(id));
|
|
210
|
+
const missingExtras = EXTRA_BME_AGENT_IDS.filter(id => !manifestContent.includes(`bmad-agent-bme-${id}`));
|
|
211
|
+
|
|
212
|
+
const allMissing = [...missingFromManifest, ...missingExtras];
|
|
213
|
+
if (allMissing.length > 0) {
|
|
214
|
+
check.error = `Agent manifest missing: ${allMissing.join(', ')}`;
|
|
215
|
+
return check;
|
|
216
|
+
}
|
|
207
217
|
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
// Confirm standalone bme agent files exist on disk
|
|
219
|
+
const missingExtraFiles = EXTRA_BME_AGENTS
|
|
220
|
+
.filter(a => !fs.existsSync(path.join(projectRoot, '_bmad', 'bme', a.submodule, 'agents', `${a.id}.md`)))
|
|
221
|
+
.map(a => a.id);
|
|
222
|
+
if (missingExtraFiles.length > 0) {
|
|
223
|
+
check.error = `Standalone bme agent files missing: ${missingExtraFiles.join(', ')}`;
|
|
210
224
|
return check;
|
|
211
225
|
}
|
|
212
226
|
|
|
@@ -468,6 +482,101 @@ async function validateEnhanceModule(projectRoot) {
|
|
|
468
482
|
return check;
|
|
469
483
|
}
|
|
470
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Validate Artifacts module installation (optional — passes if not installed)
|
|
487
|
+
* Performs 5-point verification: directory, config, workflows array, per-workflow entry, per-workflow skill wrapper
|
|
488
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
489
|
+
* @returns {Promise<object>} Validation check result
|
|
490
|
+
*/
|
|
491
|
+
async function validateArtifactsModule(projectRoot) {
|
|
492
|
+
const check = {
|
|
493
|
+
name: 'Artifacts module',
|
|
494
|
+
passed: false,
|
|
495
|
+
error: null
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
const artifactsDir = path.join(projectRoot, '_bmad/bme/_artifacts');
|
|
500
|
+
|
|
501
|
+
// Check 1: Directory exists — if not, Artifacts is simply not installed (optional)
|
|
502
|
+
if (!fs.existsSync(artifactsDir)) {
|
|
503
|
+
check.passed = true;
|
|
504
|
+
check.info = 'not installed';
|
|
505
|
+
return check;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const failures = [];
|
|
509
|
+
|
|
510
|
+
// Check 2: Config parse — bail early if config is unreadable, since later checks
|
|
511
|
+
// depend on a parsed workflows array.
|
|
512
|
+
const configPath = path.join(artifactsDir, 'config.yaml');
|
|
513
|
+
if (!fs.existsSync(configPath)) {
|
|
514
|
+
check.error = 'Artifacts: config.yaml not found';
|
|
515
|
+
return check;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let config = null;
|
|
519
|
+
try {
|
|
520
|
+
config = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
521
|
+
} catch (err) {
|
|
522
|
+
check.error = `Artifacts: config.yaml parse error: ${err.message}`;
|
|
523
|
+
return check;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (!config || typeof config !== 'object') {
|
|
527
|
+
check.error = 'Artifacts: config.yaml is empty or invalid';
|
|
528
|
+
return check;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check 3: Workflows array non-empty
|
|
532
|
+
if (!Array.isArray(config.workflows) || config.workflows.length === 0) {
|
|
533
|
+
check.error = 'Artifacts: config.yaml has no workflows array';
|
|
534
|
+
return check;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Checks 4 & 5: Per-workflow entry point and skill wrapper.
|
|
538
|
+
// Aggregate failures across all workflows so a single doctor run reports every
|
|
539
|
+
// problem at once (mirrors validateEnhanceModule).
|
|
540
|
+
// Non-standalone workflows are skipped from wrapper/entry checks because
|
|
541
|
+
// refresh-installation.js section 6d does NOT install them — validating their
|
|
542
|
+
// wrapper would be a contract mismatch with the refresh logic.
|
|
543
|
+
for (const wf of config.workflows) {
|
|
544
|
+
if (!wf || !wf.name || !wf.entry) {
|
|
545
|
+
failures.push('workflow entry missing name or entry field');
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (wf.standalone !== true) {
|
|
550
|
+
// Refresh skips non-standalone workflows; nothing to validate.
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Check 4: Workflow entry point file exists
|
|
555
|
+
const entryPath = path.join(artifactsDir, wf.entry);
|
|
556
|
+
if (!fs.existsSync(entryPath)) {
|
|
557
|
+
failures.push(`workflow entry missing for ${wf.name}: ${wf.entry}`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Check 5: Skill wrapper exists at .claude/skills/{workflow.name}/SKILL.md
|
|
561
|
+
// (workflow.name already carries the bmad- prefix; do NOT synthesize bmad-${wf.name})
|
|
562
|
+
const skillWrapperPath = path.join(projectRoot, '.claude', 'skills', wf.name, 'SKILL.md');
|
|
563
|
+
if (!fs.existsSync(skillWrapperPath)) {
|
|
564
|
+
failures.push(`skill wrapper missing for ${wf.name}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (failures.length > 0) {
|
|
569
|
+
check.error = `Artifacts: ${failures.join('; ')}`;
|
|
570
|
+
} else {
|
|
571
|
+
check.passed = true;
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
check.error = error.message;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return check;
|
|
578
|
+
}
|
|
579
|
+
|
|
471
580
|
/**
|
|
472
581
|
* Validate a SKILL.md file has required frontmatter fields
|
|
473
582
|
* @param {string} skillMdPath - Absolute path to SKILL.md file
|
|
@@ -633,6 +742,7 @@ module.exports = {
|
|
|
633
742
|
validateDeprecatedWorkflows,
|
|
634
743
|
validateWorkflowStepStructure,
|
|
635
744
|
validateEnhanceModule,
|
|
745
|
+
validateArtifactsModule,
|
|
636
746
|
validateSkillMd,
|
|
637
747
|
validateStepFiles,
|
|
638
748
|
validateSkillCohesion,
|