convoke-agents 2.4.0 → 3.0.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 +16 -0
- package/INSTALLATION.md +109 -86
- package/README.md +220 -163
- package/UPDATE-GUIDE.md +63 -23
- package/_bmad/bme/_gyre/README.md +100 -0
- package/_bmad/bme/_gyre/agents/.gitkeep +0 -0
- package/_bmad/bme/_gyre/agents/model-curator.md +128 -0
- package/_bmad/bme/_gyre/agents/readiness-analyst.md +127 -0
- package/_bmad/bme/_gyre/agents/review-coach.md +130 -0
- package/_bmad/bme/_gyre/agents/stack-detective.md +125 -0
- package/_bmad/bme/_gyre/compass-routing-reference.md +168 -0
- package/_bmad/bme/_gyre/config.yaml +22 -0
- package/_bmad/bme/_gyre/contracts/.gitkeep +0 -0
- package/_bmad/bme/_gyre/contracts/gc1-stack-profile.md +152 -0
- package/_bmad/bme/_gyre/contracts/gc2-capabilities-manifest.md +189 -0
- package/_bmad/bme/_gyre/contracts/gc3-findings-report.md +197 -0
- package/_bmad/bme/_gyre/contracts/gc4-feedback-loop.md +209 -0
- package/_bmad/bme/_gyre/guides/ATLAS-USER-GUIDE.md +177 -0
- package/_bmad/bme/_gyre/guides/COACH-USER-GUIDE.md +172 -0
- package/_bmad/bme/_gyre/guides/LENS-USER-GUIDE.md +181 -0
- package/_bmad/bme/_gyre/guides/SCOUT-USER-GUIDE.md +158 -0
- package/_bmad/bme/_gyre/workflows/.gitkeep +0 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-01-select-repos.md +55 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-02-run-validation.md +78 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-03-score-results.md +143 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/workflow.md +41 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-01-load-history.md +63 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-02-compute-delta.md +72 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-03-present-delta.md +143 -0
- package/_bmad/bme/_gyre/workflows/delta-report/workflow.md +34 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-01-initialize.md +68 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-02-detect-stack.md +49 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-03-generate-model.md +52 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-04-analyze-gaps.md +42 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-05-review-findings.md +128 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/workflow.md +39 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-01-load-manifest.md +70 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-02-observability-analysis.md +110 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-03-deployment-analysis.md +87 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-04-cross-domain-correlation.md +105 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-05-present-findings.md +172 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/workflow.md +38 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-01-load-profile.md +74 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-02-generate-capabilities.md +116 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-03-web-enrichment.md +89 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-04-write-manifest.md +122 -0
- package/_bmad/bme/_gyre/workflows/model-generation/workflow.md +40 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-01-load-context.md +86 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-02-walkthrough.md +116 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-03-apply-amendments.md +92 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-04-capture-feedback.md +107 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-05-summary.md +60 -0
- package/_bmad/bme/_gyre/workflows/model-review/workflow.md +41 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-01-scan-filesystem.md +176 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-02-classify-stack.md +111 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-03-guard-questions.md +117 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/workflow.md +42 -0
- package/_bmad/bme/_vortex/config.yaml +1 -1
- package/package.json +5 -2
- package/scripts/archive.js +304 -0
- package/scripts/convoke-doctor.js +146 -132
- package/scripts/docs-audit.js +21 -5
- package/scripts/install-gyre-agents.js +140 -0
- package/scripts/install-vortex-agents.js +0 -0
- package/scripts/update/lib/agent-registry.js +70 -0
- package/scripts/update/lib/refresh-installation.js +152 -30
- package/scripts/update/lib/validator.js +160 -1
|
@@ -133,6 +133,71 @@ const WAVE3_STREAMS = new Set(['Synthesize', 'Hypothesize', 'Sensitize']);
|
|
|
133
133
|
const _wave3AgentIds = new Set(AGENTS.filter(a => WAVE3_STREAMS.has(a.stream)).map(a => a.id));
|
|
134
134
|
const WAVE3_WORKFLOW_NAMES = new Set(WORKFLOWS.filter(w => _wave3AgentIds.has(w.agent)).map(w => w.name));
|
|
135
135
|
|
|
136
|
+
// ── Gyre Module ──────────────────────────────────────────────────────
|
|
137
|
+
const GYRE_AGENTS = [
|
|
138
|
+
{
|
|
139
|
+
id: 'stack-detective', name: 'Scout', icon: '\u{1F50E}',
|
|
140
|
+
title: 'Stack Detective', stream: 'Detect',
|
|
141
|
+
persona: {
|
|
142
|
+
role: 'Technology Stack Detective + Architecture Classification Specialist',
|
|
143
|
+
identity: 'Methodical investigator who detects project technology stacks by analyzing filesystem artifacts. Reads manifests, configs, and IaC files. Never guesses — reports what evidence supports. Asks targeted guard questions derived from detection results to confirm architecture intent. Produces the Stack Profile (GC1) that downstream agents use to generate contextual models.',
|
|
144
|
+
communication_style: 'Methodical and evidence-driven. Reports findings with source references. Says things like \'I found evidence of...\' and \'Based on the manifests, this appears to be...\' Never speculates — distinguishes confirmed detections from inferences.',
|
|
145
|
+
expertise: '- Evidence over inference — every detection claim cites a specific file or pattern - Guard questions clarify ambiguity, not confirm the obvious - Privacy boundary: Stack Profile carries categories, never file contents or secrets - Report secondary stacks as warnings, not errors - Detection is the foundation — get it right and everything downstream improves',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'model-curator', name: 'Atlas', icon: '\u{1F4D0}',
|
|
150
|
+
title: 'Model Curator', stream: 'Model',
|
|
151
|
+
persona: {
|
|
152
|
+
role: 'Contextual Model Generation + Capabilities Curation Specialist',
|
|
153
|
+
identity: 'Knowledgeable curator who generates capabilities manifests unique to each detected stack. Balances industry standards (DORA, OpenTelemetry, Google PRR) with practical relevance. Explains why each capability matters. Transparent about confidence levels — distinguishes well-known patterns from emerging practices.',
|
|
154
|
+
communication_style: 'Knowledgeable and transparent — explains reasoning behind each capability. Says things like \'This capability matters for your stack because...\' and \'I\'m less confident about this one — it\'s an emerging practice.\' Respects team ownership of the model.',
|
|
155
|
+
expertise: '- Industry standards inform but don\'t dictate — every capability must be relevant to THIS stack - Web search for current best practices keeps the model fresh - Model is team-owned — amendments from Coach (GC4) are respected on regeneration - Transparency about sources and confidence builds trust - Generate ≥20 capabilities for supported archetypes',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'readiness-analyst', name: 'Lens', icon: '\u{1F52C}',
|
|
160
|
+
title: 'Readiness Analyst', stream: 'Analyze',
|
|
161
|
+
persona: {
|
|
162
|
+
role: 'Absence Detection + Cross-Domain Correlation Specialist',
|
|
163
|
+
identity: 'Thorough analyst who compares the capabilities manifest against what actually exists in the project. Identifies absences — what\'s missing, not just what\'s misconfigured. Runs observability and deployment domain analyses with cross-domain correlation for compound findings.',
|
|
164
|
+
communication_style: 'Thorough and honest — presents findings with evidence and confidence levels. Says things like \'I found no evidence of...\' and \'These two gaps amplify each other.\' Never inflates severity — a nice-to-have stays a nice-to-have.',
|
|
165
|
+
expertise: '- Absence detection finds what\'s missing, not just what\'s broken - Source-tag every finding (static analysis vs contextual model) - Cross-domain correlation reveals compound gaps that single-domain analysis misses - Confidence levels must reflect actual evidence strength - Never inflate severity — accuracy builds credibility',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'review-coach', name: 'Coach', icon: '\u{1F3CB}',
|
|
170
|
+
title: 'Review Coach', stream: 'Review',
|
|
171
|
+
persona: {
|
|
172
|
+
role: 'Findings Review + Model Amendment + Feedback Capture Specialist',
|
|
173
|
+
identity: 'Patient guide who respects the user\'s expertise. Presents findings clearly — severity-first summary, then walkthrough. For model review, presents each capability one at a time with keep/remove/edit options. Captures feedback on missed gaps to improve the model over time.',
|
|
174
|
+
communication_style: 'Patient and respectful — lets the user decide what\'s relevant. Says things like \'Here\'s what we found — let me walk you through it\' and \'Did Gyre miss anything you know about?\' Explains why feedback matters for model improvement.',
|
|
175
|
+
expertise: '- Severity-first presentation respects the user\'s time - Model review is a conversation, not a checklist - Feedback capture improves the model for the whole team - Amendments persist — the model becomes team-owned through review - Never push — the user knows their system best',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const GYRE_WORKFLOWS = [
|
|
181
|
+
// Scout — Detect
|
|
182
|
+
{ name: 'stack-detection', agent: 'stack-detective' },
|
|
183
|
+
// Atlas — Model
|
|
184
|
+
{ name: 'model-generation', agent: 'model-curator' },
|
|
185
|
+
// Lens — Analyze
|
|
186
|
+
{ name: 'gap-analysis', agent: 'readiness-analyst' },
|
|
187
|
+
{ name: 'delta-report', agent: 'readiness-analyst' },
|
|
188
|
+
// Coach — Review
|
|
189
|
+
{ name: 'model-review', agent: 'review-coach' },
|
|
190
|
+
// Orchestration
|
|
191
|
+
{ name: 'full-analysis', agent: 'stack-detective' },
|
|
192
|
+
// Validation
|
|
193
|
+
{ name: 'accuracy-validation', agent: 'model-curator' },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
// Derived lists for Gyre
|
|
197
|
+
const GYRE_AGENT_FILES = GYRE_AGENTS.map(a => `${a.id}.md`);
|
|
198
|
+
const GYRE_AGENT_IDS = GYRE_AGENTS.map(a => a.id);
|
|
199
|
+
const GYRE_WORKFLOW_NAMES = GYRE_WORKFLOWS.map(w => w.name);
|
|
200
|
+
|
|
136
201
|
module.exports = {
|
|
137
202
|
AGENTS,
|
|
138
203
|
WORKFLOWS,
|
|
@@ -141,4 +206,9 @@ module.exports = {
|
|
|
141
206
|
WORKFLOW_NAMES,
|
|
142
207
|
USER_GUIDES,
|
|
143
208
|
WAVE3_WORKFLOW_NAMES,
|
|
209
|
+
GYRE_AGENTS,
|
|
210
|
+
GYRE_WORKFLOWS,
|
|
211
|
+
GYRE_AGENT_FILES,
|
|
212
|
+
GYRE_AGENT_IDS,
|
|
213
|
+
GYRE_WORKFLOW_NAMES,
|
|
144
214
|
};
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const yaml = require('js-yaml');
|
|
6
6
|
const { getPackageVersion } = require('./utils');
|
|
7
7
|
const configMerger = require('./config-merger');
|
|
8
|
-
const { AGENTS, AGENT_FILES, AGENT_IDS, WORKFLOW_NAMES, USER_GUIDES } = require('./agent-registry');
|
|
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
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Refresh Installation for Convoke
|
|
@@ -182,6 +182,90 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
// 2d. Gyre module — copy agents, workflows, contracts, config
|
|
186
|
+
const packageGyre = path.join(packageRoot, '_bmad', 'bme', '_gyre');
|
|
187
|
+
const targetGyre = path.join(projectRoot, '_bmad', 'bme', '_gyre');
|
|
188
|
+
|
|
189
|
+
if (fs.existsSync(packageGyre)) {
|
|
190
|
+
// Copy Gyre agents
|
|
191
|
+
const gyreAgentsSource = path.join(packageGyre, 'agents');
|
|
192
|
+
const gyreAgentsTarget = path.join(targetGyre, 'agents');
|
|
193
|
+
await fs.ensureDir(gyreAgentsTarget);
|
|
194
|
+
|
|
195
|
+
if (!isSameRoot) {
|
|
196
|
+
for (const file of GYRE_AGENT_FILES) {
|
|
197
|
+
const src = path.join(gyreAgentsSource, file);
|
|
198
|
+
if (fs.existsSync(src)) {
|
|
199
|
+
await fs.copy(src, path.join(gyreAgentsTarget, file), { overwrite: true });
|
|
200
|
+
changes.push(`Refreshed Gyre agent: ${file}`);
|
|
201
|
+
if (verbose) console.log(` Refreshed Gyre agent: ${file}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
changes.push('Skipped Gyre agent copy (dev environment)');
|
|
206
|
+
if (verbose) console.log(' Skipped Gyre agent copy (dev environment)');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Copy Gyre workflows
|
|
210
|
+
const gyreWorkflowsSource = path.join(packageGyre, 'workflows');
|
|
211
|
+
const gyreWorkflowsTarget = path.join(targetGyre, 'workflows');
|
|
212
|
+
await fs.ensureDir(gyreWorkflowsTarget);
|
|
213
|
+
|
|
214
|
+
if (!isSameRoot) {
|
|
215
|
+
for (const wf of GYRE_WORKFLOW_NAMES) {
|
|
216
|
+
const src = path.join(gyreWorkflowsSource, wf);
|
|
217
|
+
const dest = path.join(gyreWorkflowsTarget, wf);
|
|
218
|
+
if (fs.existsSync(src)) {
|
|
219
|
+
if (fs.existsSync(dest)) {
|
|
220
|
+
await fs.remove(dest);
|
|
221
|
+
}
|
|
222
|
+
await fs.copy(src, dest, { overwrite: true });
|
|
223
|
+
changes.push(`Refreshed Gyre workflow: ${wf}`);
|
|
224
|
+
if (verbose) console.log(` Refreshed Gyre workflow: ${wf}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
changes.push('Skipped Gyre workflow copy (dev environment)');
|
|
229
|
+
if (verbose) console.log(' Skipped Gyre workflow copy (dev environment)');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Copy Gyre contracts
|
|
233
|
+
const gyreContractsSource = path.join(packageGyre, 'contracts');
|
|
234
|
+
const gyreContractsTarget = path.join(targetGyre, 'contracts');
|
|
235
|
+
if (fs.existsSync(gyreContractsSource)) {
|
|
236
|
+
await fs.ensureDir(gyreContractsTarget);
|
|
237
|
+
if (!isSameRoot) {
|
|
238
|
+
await fs.copy(gyreContractsSource, gyreContractsTarget, { overwrite: true });
|
|
239
|
+
changes.push('Refreshed Gyre contracts');
|
|
240
|
+
if (verbose) console.log(' Refreshed Gyre contracts');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Copy Gyre config.yaml
|
|
245
|
+
const gyreConfigSource = path.join(packageGyre, 'config.yaml');
|
|
246
|
+
const gyreConfigTarget = path.join(targetGyre, 'config.yaml');
|
|
247
|
+
if (!isSameRoot && fs.existsSync(gyreConfigSource)) {
|
|
248
|
+
// Merge Gyre config preserving user prefs, same as Vortex
|
|
249
|
+
const gyreUpdates = {
|
|
250
|
+
agents: GYRE_AGENT_IDS,
|
|
251
|
+
workflows: GYRE_WORKFLOW_NAMES
|
|
252
|
+
};
|
|
253
|
+
const gyreConfigMerged = await configMerger.mergeConfig(gyreConfigTarget, version, gyreUpdates);
|
|
254
|
+
await configMerger.writeConfig(gyreConfigTarget, gyreConfigMerged);
|
|
255
|
+
changes.push(`Updated Gyre config.yaml to v${version}`);
|
|
256
|
+
if (verbose) console.log(` Updated Gyre config.yaml to v${version}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Copy Gyre README
|
|
260
|
+
const gyreReadmeSource = path.join(packageGyre, 'README.md');
|
|
261
|
+
const gyreReadmeTarget = path.join(targetGyre, 'README.md');
|
|
262
|
+
if (!isSameRoot && fs.existsSync(gyreReadmeSource)) {
|
|
263
|
+
await fs.copy(gyreReadmeSource, gyreReadmeTarget, { overwrite: true });
|
|
264
|
+
changes.push('Refreshed Gyre README.md');
|
|
265
|
+
if (verbose) console.log(' Refreshed Gyre README.md');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
185
269
|
// 3. Update config.yaml (merge, preserving user prefs)
|
|
186
270
|
const configPath = path.join(targetVortex, 'config.yaml');
|
|
187
271
|
await fs.ensureDir(path.dirname(configPath));
|
|
@@ -264,35 +348,45 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
264
348
|
isV610 = true;
|
|
265
349
|
}
|
|
266
350
|
|
|
267
|
-
// Build fresh bme rows matching the detected schema
|
|
351
|
+
// Build fresh bme rows matching the detected schema (Vortex + Gyre agents)
|
|
352
|
+
function buildAgentRow610(a, submodule) {
|
|
353
|
+
const p = a.persona;
|
|
354
|
+
return [
|
|
355
|
+
csvEscape(a.name), // name
|
|
356
|
+
csvEscape(''), // displayName
|
|
357
|
+
csvEscape(a.title), // title
|
|
358
|
+
csvEscape(a.icon), // icon
|
|
359
|
+
csvEscape(''), // capabilities
|
|
360
|
+
csvEscape(p.role), // role
|
|
361
|
+
csvEscape(p.identity), // identity
|
|
362
|
+
csvEscape(p.communication_style), // communicationStyle
|
|
363
|
+
csvEscape(p.expertise), // principles
|
|
364
|
+
csvEscape('bme'), // module
|
|
365
|
+
csvEscape(`_bmad/bme/${submodule}/agents/${a.id}.md`), // path
|
|
366
|
+
csvEscape(`bmad-agent-bme-${a.id}`), // canonicalId
|
|
367
|
+
].join(',');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function buildAgentRowLegacy(a, submodule) {
|
|
371
|
+
const p = a.persona;
|
|
372
|
+
return [
|
|
373
|
+
a.id, a.name, a.title, a.icon,
|
|
374
|
+
p.role, p.identity, p.communication_style, p.expertise,
|
|
375
|
+
'bme', `_bmad/bme/${submodule}/agents/${a.id}.md`,
|
|
376
|
+
].map(csvEscape).join(',');
|
|
377
|
+
}
|
|
378
|
+
|
|
268
379
|
let bmeRows;
|
|
269
380
|
if (isV610) {
|
|
270
|
-
bmeRows =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
csvEscape(''), // displayName
|
|
275
|
-
csvEscape(a.title), // title
|
|
276
|
-
csvEscape(a.icon), // icon
|
|
277
|
-
csvEscape(''), // capabilities
|
|
278
|
-
csvEscape(p.role), // role
|
|
279
|
-
csvEscape(p.identity), // identity
|
|
280
|
-
csvEscape(p.communication_style), // communicationStyle
|
|
281
|
-
csvEscape(p.expertise), // principles
|
|
282
|
-
csvEscape('bme'), // module
|
|
283
|
-
csvEscape(`_bmad/bme/_vortex/agents/${a.id}.md`), // path
|
|
284
|
-
csvEscape(`bmad-agent-bme-${a.id}`), // canonicalId
|
|
285
|
-
].join(',');
|
|
286
|
-
});
|
|
381
|
+
bmeRows = [
|
|
382
|
+
...AGENTS.map(a => buildAgentRow610(a, '_vortex')),
|
|
383
|
+
...GYRE_AGENTS.map(a => buildAgentRow610(a, '_gyre')),
|
|
384
|
+
];
|
|
287
385
|
} else {
|
|
288
|
-
bmeRows =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
p.role, p.identity, p.communication_style, p.expertise,
|
|
293
|
-
'bme', `_bmad/bme/_vortex/agents/${a.id}.md`,
|
|
294
|
-
].map(csvEscape).join(',');
|
|
295
|
-
});
|
|
386
|
+
bmeRows = [
|
|
387
|
+
...AGENTS.map(a => buildAgentRowLegacy(a, '_vortex')),
|
|
388
|
+
...GYRE_AGENTS.map(a => buildAgentRowLegacy(a, '_gyre')),
|
|
389
|
+
];
|
|
296
390
|
}
|
|
297
391
|
|
|
298
392
|
const allRows = [...preservedRows, ...bmeRows].join('\n') + '\n';
|
|
@@ -342,7 +436,10 @@ async function refreshInstallation(projectRoot, options = {}) {
|
|
|
342
436
|
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
343
437
|
|
|
344
438
|
// Remove stale skill directories (agents no longer in registry)
|
|
345
|
-
const currentSkillDirs = new Set(
|
|
439
|
+
const currentSkillDirs = new Set([
|
|
440
|
+
...AGENTS.map(a => `bmad-agent-bme-${a.id}`),
|
|
441
|
+
...GYRE_AGENTS.map(a => `bmad-agent-bme-${a.id}`),
|
|
442
|
+
]);
|
|
346
443
|
if (fs.existsSync(skillsDir)) {
|
|
347
444
|
const existingSkills = (await fs.readdir(skillsDir)).filter(d => d.startsWith('bmad-agent-bme-'));
|
|
348
445
|
for (const dir of existingSkills) {
|
|
@@ -378,7 +475,32 @@ You must fully embody this agent's persona and follow all activation instruction
|
|
|
378
475
|
if (verbose) console.log(` Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
379
476
|
}
|
|
380
477
|
|
|
381
|
-
//
|
|
478
|
+
// 6b. Generate .claude/skills/ for Gyre agents
|
|
479
|
+
for (const agent of GYRE_AGENTS) {
|
|
480
|
+
const skillDir = path.join(skillsDir, `bmad-agent-bme-${agent.id}`);
|
|
481
|
+
await fs.ensureDir(skillDir);
|
|
482
|
+
const content = `---
|
|
483
|
+
name: bmad-agent-bme-${agent.id}
|
|
484
|
+
description: ${agent.id} agent
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
488
|
+
|
|
489
|
+
<agent-activation CRITICAL="TRUE">
|
|
490
|
+
1. LOAD the FULL agent file from {project-root}/_bmad/bme/_gyre/agents/${agent.id}.md
|
|
491
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
492
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
493
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
494
|
+
5. PRESENT the numbered menu
|
|
495
|
+
6. WAIT for user input before proceeding
|
|
496
|
+
</agent-activation>
|
|
497
|
+
`;
|
|
498
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), content, 'utf8');
|
|
499
|
+
changes.push(`Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
500
|
+
if (verbose) console.log(` Refreshed skill: bmad-agent-bme-${agent.id}/SKILL.md`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 6c. Copy Enhance workflow skill wrappers and register in manifests
|
|
382
504
|
if (enhanceConfig && !isSameRoot) {
|
|
383
505
|
for (const workflow of enhanceConfig.workflows || []) {
|
|
384
506
|
const canonicalId = `bmad-enhance-${workflow.name}`;
|
|
@@ -457,7 +579,7 @@ menu: []
|
|
|
457
579
|
prompts: []
|
|
458
580
|
`;
|
|
459
581
|
|
|
460
|
-
for (const agent of AGENTS) {
|
|
582
|
+
for (const agent of [...AGENTS, ...GYRE_AGENTS]) {
|
|
461
583
|
const filename = `bme-${agent.name.toLowerCase()}.customize.yaml`;
|
|
462
584
|
const filePath = path.join(customizeDir, filename);
|
|
463
585
|
if (!fs.existsSync(filePath)) {
|
|
@@ -468,6 +468,161 @@ async function validateEnhanceModule(projectRoot) {
|
|
|
468
468
|
return check;
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Validate a SKILL.md file has required frontmatter fields
|
|
473
|
+
* @param {string} skillMdPath - Absolute path to SKILL.md file
|
|
474
|
+
* @returns {Promise<object>} Validation result { valid, errors }
|
|
475
|
+
*/
|
|
476
|
+
async function validateSkillMd(skillMdPath) {
|
|
477
|
+
const errors = [];
|
|
478
|
+
|
|
479
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
480
|
+
errors.push(`SKILL.md not found: ${skillMdPath}`);
|
|
481
|
+
return { valid: false, errors };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
485
|
+
|
|
486
|
+
// Extract YAML frontmatter between --- delimiters
|
|
487
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
488
|
+
if (!fmMatch) {
|
|
489
|
+
errors.push('SKILL.md missing YAML frontmatter (--- delimiters)');
|
|
490
|
+
return { valid: false, errors };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
let frontmatter;
|
|
494
|
+
try {
|
|
495
|
+
frontmatter = yaml.load(fmMatch[1]);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
errors.push(`SKILL.md frontmatter parse error: ${err.message}`);
|
|
498
|
+
return { valid: false, errors };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!frontmatter || typeof frontmatter !== 'object') {
|
|
502
|
+
errors.push('SKILL.md frontmatter is empty or not an object');
|
|
503
|
+
return { valid: false, errors };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (!frontmatter.name || typeof frontmatter.name !== 'string') {
|
|
507
|
+
errors.push('SKILL.md missing required field: name');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!frontmatter.description || typeof frontmatter.description !== 'string') {
|
|
511
|
+
errors.push('SKILL.md missing required field: description');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return { valid: errors.length === 0, errors };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Validate step files in a skill directory follow sequential numbering
|
|
519
|
+
* @param {string} skillDir - Absolute path to skill directory
|
|
520
|
+
* @returns {Promise<object>} Validation result { valid, errors }
|
|
521
|
+
*/
|
|
522
|
+
async function validateStepFiles(skillDir) {
|
|
523
|
+
const errors = [];
|
|
524
|
+
|
|
525
|
+
if (!fs.existsSync(skillDir)) {
|
|
526
|
+
errors.push(`Skill directory not found: ${skillDir}`);
|
|
527
|
+
return { valid: false, errors };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Collect step files from skill dir and steps/ subdirectory
|
|
531
|
+
const stepFiles = [];
|
|
532
|
+
const entries = fs.readdirSync(skillDir);
|
|
533
|
+
|
|
534
|
+
for (const entry of entries) {
|
|
535
|
+
if (/^step-\d+-/.test(entry) && entry.endsWith('.md')) {
|
|
536
|
+
stepFiles.push(entry);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const stepsSubdir = path.join(skillDir, 'steps');
|
|
541
|
+
if (fs.existsSync(stepsSubdir)) {
|
|
542
|
+
const subEntries = fs.readdirSync(stepsSubdir);
|
|
543
|
+
for (const entry of subEntries) {
|
|
544
|
+
if (/^step-\d+-/.test(entry) && entry.endsWith('.md')) {
|
|
545
|
+
stepFiles.push(entry);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (stepFiles.length === 0) {
|
|
551
|
+
// No step files is valid — some skills are single-file (agent-activation type)
|
|
552
|
+
return { valid: true, errors };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Extract step numbers and check for sequential numbering
|
|
556
|
+
const stepNumbers = stepFiles
|
|
557
|
+
.map(f => {
|
|
558
|
+
const match = f.match(/^step-(\d+)-/);
|
|
559
|
+
return match ? parseInt(match[1], 10) : null;
|
|
560
|
+
})
|
|
561
|
+
.filter(n => n !== null)
|
|
562
|
+
.sort((a, b) => a - b);
|
|
563
|
+
|
|
564
|
+
// Remove duplicates (same step number in root and steps/)
|
|
565
|
+
const uniqueSteps = [...new Set(stepNumbers)];
|
|
566
|
+
|
|
567
|
+
// Check for gaps in sequence
|
|
568
|
+
for (let i = 0; i < uniqueSteps.length - 1; i++) {
|
|
569
|
+
if (uniqueSteps[i + 1] - uniqueSteps[i] > 1) {
|
|
570
|
+
errors.push(
|
|
571
|
+
`Step numbering gap: step-${String(uniqueSteps[i]).padStart(2, '0')} to step-${String(uniqueSteps[i + 1]).padStart(2, '0')} (missing step-${String(uniqueSteps[i] + 1).padStart(2, '0')})`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return { valid: errors.length === 0, errors };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Validate skill cohesion — workflow.md exists if step files exist
|
|
581
|
+
* @param {string} skillDir - Absolute path to skill directory
|
|
582
|
+
* @returns {Promise<object>} Validation result { valid, errors }
|
|
583
|
+
*/
|
|
584
|
+
async function validateSkillCohesion(skillDir) {
|
|
585
|
+
const errors = [];
|
|
586
|
+
|
|
587
|
+
if (!fs.existsSync(skillDir)) {
|
|
588
|
+
errors.push(`Skill directory not found: ${skillDir}`);
|
|
589
|
+
return { valid: false, errors };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const entries = fs.readdirSync(skillDir);
|
|
593
|
+
const hasStepFiles = entries.some(e => /^step-\d+-/.test(e) && e.endsWith('.md'));
|
|
594
|
+
const hasStepsSubdir = fs.existsSync(path.join(skillDir, 'steps'));
|
|
595
|
+
const hasWorkflow = entries.includes('workflow.md');
|
|
596
|
+
|
|
597
|
+
// If step files or steps/ subdirectory exist, workflow.md should too
|
|
598
|
+
if ((hasStepFiles || hasStepsSubdir) && !hasWorkflow) {
|
|
599
|
+
errors.push('Skill has step files but no workflow.md');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return { valid: errors.length === 0, errors };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Validate a complete skill package (SKILL.md + step files + cohesion)
|
|
607
|
+
* @param {string} skillDir - Absolute path to skill directory
|
|
608
|
+
* @returns {Promise<object>} Validation result { valid, errors }
|
|
609
|
+
*/
|
|
610
|
+
async function validateSkill(skillDir) {
|
|
611
|
+
const allErrors = [];
|
|
612
|
+
|
|
613
|
+
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
614
|
+
const skillMdResult = await validateSkillMd(skillMdPath);
|
|
615
|
+
allErrors.push(...skillMdResult.errors);
|
|
616
|
+
|
|
617
|
+
const stepResult = await validateStepFiles(skillDir);
|
|
618
|
+
allErrors.push(...stepResult.errors);
|
|
619
|
+
|
|
620
|
+
const cohesionResult = await validateSkillCohesion(skillDir);
|
|
621
|
+
allErrors.push(...cohesionResult.errors);
|
|
622
|
+
|
|
623
|
+
return { valid: allErrors.length === 0, errors: allErrors };
|
|
624
|
+
}
|
|
625
|
+
|
|
471
626
|
module.exports = {
|
|
472
627
|
validateInstallation,
|
|
473
628
|
validateConfigStructure,
|
|
@@ -477,5 +632,9 @@ module.exports = {
|
|
|
477
632
|
validateUserDataIntegrity,
|
|
478
633
|
validateDeprecatedWorkflows,
|
|
479
634
|
validateWorkflowStepStructure,
|
|
480
|
-
validateEnhanceModule
|
|
635
|
+
validateEnhanceModule,
|
|
636
|
+
validateSkillMd,
|
|
637
|
+
validateStepFiles,
|
|
638
|
+
validateSkillCohesion,
|
|
639
|
+
validateSkill
|
|
481
640
|
};
|