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
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* convoke-export.js — Story sp-2-3
|
|
4
|
+
*
|
|
5
|
+
* CLI entry point for the Tier 1 skill exporter. Wraps `exportSkill()` from
|
|
6
|
+
* sp-2-2's export-engine.js, writes per-skill `instructions.md` + `README.md`
|
|
7
|
+
* to disk, and reports success/failure with stable grep-friendly lines.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* convoke-export <skill-name> # single skill, default output
|
|
11
|
+
* convoke-export <skill-name> --output <dir>
|
|
12
|
+
* convoke-export --tier 1 # batch all standalone skills
|
|
13
|
+
* convoke-export --all # alias for --tier 1 (sp-2-3)
|
|
14
|
+
* convoke-export --tier 1 --dry-run # preview without writing
|
|
15
|
+
* convoke-export --help
|
|
16
|
+
*
|
|
17
|
+
* The CLI is read-only on the source tree — only the --output directory
|
|
18
|
+
* (or the default ./exported-skills/) is written.
|
|
19
|
+
*
|
|
20
|
+
* See _bmad-output/implementation-artifacts/sp-2-3-cli-entry-point.md for the
|
|
21
|
+
* full spec.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const { findProjectRoot } = require('../update/lib/utils');
|
|
29
|
+
const { exportSkill, humanizeSkillName } = require('./export-engine');
|
|
30
|
+
const { readManifest } = require('./manifest-csv');
|
|
31
|
+
const { generateAdapters } = require('./generate-adapters');
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// EXIT CODES
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
const EXIT_SUCCESS = 0;
|
|
38
|
+
const EXIT_USAGE = 1;
|
|
39
|
+
const EXIT_NOT_FOUND = 2;
|
|
40
|
+
const EXIT_TIER_NOT_SUPPORTED = 3;
|
|
41
|
+
const EXIT_PARTIAL_FAILURE = 4;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// ARG PARSER (hand-rolled, no deps — matches classify-skills.js style)
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse argv into a simple options object.
|
|
49
|
+
* Returns { positional: string[], output: string|null, tier: string|null,
|
|
50
|
+
* all: boolean, dryRun: boolean, help: boolean, unknown: string|null }
|
|
51
|
+
*/
|
|
52
|
+
function parseArgs(argv) {
|
|
53
|
+
const opts = {
|
|
54
|
+
positional: [],
|
|
55
|
+
output: null,
|
|
56
|
+
tier: null,
|
|
57
|
+
all: false,
|
|
58
|
+
dryRun: false,
|
|
59
|
+
help: false,
|
|
60
|
+
unknown: null,
|
|
61
|
+
};
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const a = argv[i];
|
|
64
|
+
if (a === '--help' || a === '-h') {
|
|
65
|
+
opts.help = true;
|
|
66
|
+
} else if (a === '--dry-run') {
|
|
67
|
+
opts.dryRun = true;
|
|
68
|
+
} else if (a === '--all') {
|
|
69
|
+
opts.all = true;
|
|
70
|
+
} else if (a === '--output') {
|
|
71
|
+
const next = argv[i + 1];
|
|
72
|
+
if (!next || next.startsWith('--')) {
|
|
73
|
+
opts.unknown = '--output (missing value)';
|
|
74
|
+
return opts;
|
|
75
|
+
}
|
|
76
|
+
opts.output = argv[++i];
|
|
77
|
+
} else if (a.startsWith('--output=')) {
|
|
78
|
+
const val = a.slice('--output='.length);
|
|
79
|
+
if (!val) { opts.unknown = '--output= (empty value)'; return opts; }
|
|
80
|
+
opts.output = val;
|
|
81
|
+
} else if (a === '--tier') {
|
|
82
|
+
const next = argv[i + 1];
|
|
83
|
+
if (!next || next.startsWith('--')) {
|
|
84
|
+
opts.unknown = '--tier (missing value)';
|
|
85
|
+
return opts;
|
|
86
|
+
}
|
|
87
|
+
opts.tier = argv[++i];
|
|
88
|
+
} else if (a.startsWith('--tier=')) {
|
|
89
|
+
const val = a.slice('--tier='.length);
|
|
90
|
+
if (!val) { opts.unknown = '--tier= (empty value)'; return opts; }
|
|
91
|
+
opts.tier = val;
|
|
92
|
+
} else if (a.startsWith('--')) {
|
|
93
|
+
opts.unknown = a;
|
|
94
|
+
return opts;
|
|
95
|
+
} else {
|
|
96
|
+
opts.positional.push(a);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return opts;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// HELP TEXT (ASCII only — no emoji)
|
|
104
|
+
// =============================================================================
|
|
105
|
+
|
|
106
|
+
function printHelp() {
|
|
107
|
+
const lines = [
|
|
108
|
+
'Usage: convoke-export <skill-name> [options]',
|
|
109
|
+
' convoke-export --tier <N> [options]',
|
|
110
|
+
' convoke-export --all [options]',
|
|
111
|
+
' convoke-export --help',
|
|
112
|
+
'',
|
|
113
|
+
'Description:',
|
|
114
|
+
' Export a BMAD skill (Tier 1 standalone or Tier 2 light-deps) to a portable',
|
|
115
|
+
' per-skill directory containing instructions.md and a README.md. Wraps the',
|
|
116
|
+
' export engine. Read-only on the source tree.',
|
|
117
|
+
'',
|
|
118
|
+
'Flags:',
|
|
119
|
+
' <skill-name> Positional. Manifest skill name (e.g. bmad-brainstorming).',
|
|
120
|
+
' --output <path> Output directory root. Defaults to ./exported-skills/',
|
|
121
|
+
' relative to the project root. User-supplied paths are',
|
|
122
|
+
' resolved against the current working directory.',
|
|
123
|
+
' --tier <value> Batch-export by tier. Accepts: 1/standalone, 2/light-deps',
|
|
124
|
+
' (both proceed); 3/pipeline (rejected).',
|
|
125
|
+
' Any other value exits 1 (usage error).',
|
|
126
|
+
' --all Export all exportable tiers (standalone + light-deps).',
|
|
127
|
+
' --dry-run Run the engine in-memory; print would-be paths; write',
|
|
128
|
+
' nothing. Combinable with all other flags.',
|
|
129
|
+
' --help, -h Print this message and exit 0.',
|
|
130
|
+
'',
|
|
131
|
+
' Conflicts:',
|
|
132
|
+
' - <skill-name> with --tier or --all -> exit 1',
|
|
133
|
+
' - --tier with --all -> exit 1',
|
|
134
|
+
' - unknown flag -> exit 1',
|
|
135
|
+
'',
|
|
136
|
+
'Exit codes:',
|
|
137
|
+
' 0 Success (or empty batch, --help, --dry-run with no failures)',
|
|
138
|
+
' 1 Usage error (unknown flag, conflicting flags, invalid --tier value)',
|
|
139
|
+
' 2 Skill not found in manifest (single-skill mode)',
|
|
140
|
+
' 3 Tier not supported (Tier 3 / pipeline requested)',
|
|
141
|
+
' 4 Partial failure (batch mode with at least one failed skill)',
|
|
142
|
+
'',
|
|
143
|
+
'Examples:',
|
|
144
|
+
' Example: single skill, default output',
|
|
145
|
+
' convoke-export bmad-brainstorming',
|
|
146
|
+
'',
|
|
147
|
+
' Example: single skill, custom output',
|
|
148
|
+
' convoke-export bmad-brainstorming --output ./out',
|
|
149
|
+
'',
|
|
150
|
+
' Example: batch tier 1, dry-run preview',
|
|
151
|
+
' convoke-export --tier 1 --dry-run',
|
|
152
|
+
'',
|
|
153
|
+
' Example: batch all (standalone + light-deps)',
|
|
154
|
+
' convoke-export --all',
|
|
155
|
+
'',
|
|
156
|
+
];
|
|
157
|
+
process.stdout.write(lines.join('\n'));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// =============================================================================
|
|
161
|
+
// REPORTER
|
|
162
|
+
// =============================================================================
|
|
163
|
+
|
|
164
|
+
function makeReporter() {
|
|
165
|
+
const results = { success: 0, failed: 0, skipped: 0, warnings: 0 };
|
|
166
|
+
return {
|
|
167
|
+
success(skill, relPath, warnings) {
|
|
168
|
+
results.success++;
|
|
169
|
+
results.warnings += warnings;
|
|
170
|
+
const suffix = warnings > 0 ? ` (${warnings} warnings)` : '';
|
|
171
|
+
process.stdout.write(`✅ ${skill} → ${relPath}${suffix}\n`);
|
|
172
|
+
},
|
|
173
|
+
failure(skill, error) {
|
|
174
|
+
results.failed++;
|
|
175
|
+
const msg = (error && error.message ? error.message : String(error)).split('\n')[0];
|
|
176
|
+
process.stderr.write(`❌ ${skill} — ${msg}\n`);
|
|
177
|
+
},
|
|
178
|
+
skip(skill, reason) {
|
|
179
|
+
results.skipped++;
|
|
180
|
+
process.stdout.write(`⏭️ ${skill} — ${reason}\n`);
|
|
181
|
+
},
|
|
182
|
+
summary(dryRun) {
|
|
183
|
+
const prefix = dryRun ? '[DRY RUN] ' : '';
|
|
184
|
+
const total = results.success + results.failed + results.skipped;
|
|
185
|
+
process.stdout.write(
|
|
186
|
+
`${prefix}Exported ${total} skills (${results.success} success, ${results.failed} failed, ${results.skipped} skipped) — ${results.warnings} warnings total\n`
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
counts() {
|
|
190
|
+
return results;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// README STUB GENERATION (Task 4)
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Read the readme template once. Cached after first call.
|
|
201
|
+
*/
|
|
202
|
+
let _templateCache = null;
|
|
203
|
+
function loadReadmeTemplate(projectRoot) {
|
|
204
|
+
if (_templateCache !== null) return _templateCache;
|
|
205
|
+
const tplPath = path.join(
|
|
206
|
+
projectRoot,
|
|
207
|
+
'scripts',
|
|
208
|
+
'portability',
|
|
209
|
+
'templates',
|
|
210
|
+
'readme-template.md'
|
|
211
|
+
);
|
|
212
|
+
_templateCache = fs.readFileSync(tplPath, 'utf8');
|
|
213
|
+
return _templateCache;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Build a per-skill README from manifest row + engine result.
|
|
218
|
+
* Reads the template, substitutes placeholders, strips HTML comments,
|
|
219
|
+
* cleans up leaked engine placeholders, and collapses whitespace.
|
|
220
|
+
* Throws if any multi-word placeholder remains after substitution.
|
|
221
|
+
*/
|
|
222
|
+
function buildReadme(skillRow, result, projectRoot) {
|
|
223
|
+
const template = loadReadmeTemplate(projectRoot);
|
|
224
|
+
const persona = result.persona || {};
|
|
225
|
+
|
|
226
|
+
const displayName = humanizeSkillName(skillRow.name);
|
|
227
|
+
const nameWithIcon = persona.icon ? `${persona.name} ${persona.icon}` : persona.name || '';
|
|
228
|
+
const commStyle =
|
|
229
|
+
(persona.communicationStyle && persona.communicationStyle.trim()) ||
|
|
230
|
+
(persona.identity && persona.identity.trim()) ||
|
|
231
|
+
'See instructions.md for details.';
|
|
232
|
+
|
|
233
|
+
// Strip the heading from "What you produce" — keep the body only.
|
|
234
|
+
const whatYouProduceBody = (result.sections.whatYouProduce || '')
|
|
235
|
+
.replace(/^##\s+What you produce\s*\n+/, '')
|
|
236
|
+
.trim();
|
|
237
|
+
|
|
238
|
+
// Parse the "Use when:" bullet block from whenToUse.
|
|
239
|
+
const whenSection = result.sections.whenToUse || '';
|
|
240
|
+
const bulletLines = whenSection
|
|
241
|
+
.split('\n')
|
|
242
|
+
.filter((l) => /^\s*-\s+/.test(l))
|
|
243
|
+
.map((l) => l.replace(/^\s+/, ''));
|
|
244
|
+
const triggerList =
|
|
245
|
+
bulletLines.length > 0
|
|
246
|
+
? bulletLines.join('\n')
|
|
247
|
+
: '- See instructions.md for trigger conditions';
|
|
248
|
+
|
|
249
|
+
// Substitute placeholders. Order matters where one token is a prefix of another.
|
|
250
|
+
let out = template;
|
|
251
|
+
out = out.replaceAll('<Skill display name>', displayName);
|
|
252
|
+
out = out.replaceAll('<persona name + icon>', nameWithIcon);
|
|
253
|
+
out = out.replaceAll('<persona name>', persona.name || '');
|
|
254
|
+
out = out.replaceAll(
|
|
255
|
+
'<one-paragraph description of what the skill does and what value it delivers>',
|
|
256
|
+
skillRow.description || ''
|
|
257
|
+
);
|
|
258
|
+
out = out.replaceAll('<persona communication style summary>', commStyle);
|
|
259
|
+
out = out.replaceAll('<output artifact description>', whatYouProduceBody);
|
|
260
|
+
out = out.replaceAll('<trigger-list>', triggerList);
|
|
261
|
+
out = out.replaceAll('<skill-name>', skillRow.name);
|
|
262
|
+
out = out.replaceAll('<tier>', skillRow.tier);
|
|
263
|
+
out = out.replaceAll('<standalone | light-deps | pipeline>', skillRow.tier);
|
|
264
|
+
|
|
265
|
+
// Sanity check: verify no multi-word <placeholder> tokens remain BEFORE
|
|
266
|
+
// stripping HTML comments (comments contain < chars that are fine).
|
|
267
|
+
const checkContent = out.replace(/<!--[\s\S]*?-->/g, '');
|
|
268
|
+
const leftover = checkContent.match(/<[a-z][a-z\s-]{2,}[a-z]>/gi);
|
|
269
|
+
if (leftover && leftover.length > 0) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`README generation left unsubstituted placeholders: ${leftover.join(', ')}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Strip HTML comments (developer docs in template, not user-facing)
|
|
276
|
+
out = out.replace(/<!--[\s\S]*?-->/g, '');
|
|
277
|
+
|
|
278
|
+
// Clean up leaked engine placeholders from Phase 6 catch-all (all 6 mapped vars + catch-all)
|
|
279
|
+
out = out.replaceAll('[your output folder]', 'your-output-folder');
|
|
280
|
+
out = out.replaceAll('[your name]', 'your-name');
|
|
281
|
+
out = out.replaceAll('[your preferred language]', 'your-preferred-language');
|
|
282
|
+
out = out.replaceAll('[your document language]', 'your-document-language');
|
|
283
|
+
out = out.replaceAll('[your planning artifacts directory]', 'your-planning-artifacts');
|
|
284
|
+
out = out.replaceAll('[your implementation artifacts directory]', 'your-implementation-artifacts');
|
|
285
|
+
out = out.replaceAll('[your context]', 'your-project-context');
|
|
286
|
+
|
|
287
|
+
// Collapse multiple blank lines and trim
|
|
288
|
+
out = out.replace(/\n{3,}/g, '\n\n').trim() + '\n';
|
|
289
|
+
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// SINGLE-SKILL EXPORT
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Export one skill. Returns { ok: bool, exitCode: number, error?: Error }.
|
|
299
|
+
* Reporter is updated as a side effect.
|
|
300
|
+
*/
|
|
301
|
+
function runSingle(skillName, outputBase, dryRun, projectRoot, reporter) {
|
|
302
|
+
let result;
|
|
303
|
+
try {
|
|
304
|
+
result = exportSkill(skillName, projectRoot);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
reporter.failure(skillName, err);
|
|
307
|
+
const msg = err.message || '';
|
|
308
|
+
if (msg.includes('not in the manifest')) {
|
|
309
|
+
return { ok: false, exitCode: EXIT_NOT_FOUND, error: err };
|
|
310
|
+
}
|
|
311
|
+
if (msg.includes('not standalone') || msg.includes('tier "')) {
|
|
312
|
+
return { ok: false, exitCode: EXIT_TIER_NOT_SUPPORTED, error: err };
|
|
313
|
+
}
|
|
314
|
+
return { ok: false, exitCode: EXIT_PARTIAL_FAILURE, error: err };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Re-read the skill row for README stub generation
|
|
318
|
+
const manifestPath = path.join(projectRoot, '_bmad', '_config', 'skill-manifest.csv');
|
|
319
|
+
const { header, rows } = readManifest(manifestPath);
|
|
320
|
+
const nameIdx = header.indexOf('name');
|
|
321
|
+
const row = rows.find((r) => r[nameIdx] === skillName);
|
|
322
|
+
if (!row) {
|
|
323
|
+
const err = new Error(`Manifest row for "${skillName}" disappeared between engine call and README generation`);
|
|
324
|
+
reporter.failure(skillName, err);
|
|
325
|
+
return { ok: false, exitCode: EXIT_PARTIAL_FAILURE, error: err };
|
|
326
|
+
}
|
|
327
|
+
const skillRow = {};
|
|
328
|
+
for (let i = 0; i < header.length; i++) skillRow[header[i]] = row[i];
|
|
329
|
+
|
|
330
|
+
let readme;
|
|
331
|
+
try {
|
|
332
|
+
readme = buildReadme(skillRow, result, projectRoot);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
reporter.failure(skillName, err);
|
|
335
|
+
return { ok: false, exitCode: EXIT_PARTIAL_FAILURE, error: err };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const skillDir = path.join(outputBase, skillName);
|
|
339
|
+
const instructionsPath = path.join(skillDir, 'instructions.md');
|
|
340
|
+
const readmePath = path.join(skillDir, 'README.md');
|
|
341
|
+
const relInstructions = path.relative(projectRoot, instructionsPath);
|
|
342
|
+
|
|
343
|
+
if (!dryRun) {
|
|
344
|
+
try {
|
|
345
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
346
|
+
fs.writeFileSync(instructionsPath, result.instructions);
|
|
347
|
+
fs.writeFileSync(readmePath, readme);
|
|
348
|
+
generateAdapters(skillName, skillRow, result.instructions, skillDir);
|
|
349
|
+
} catch (writeErr) {
|
|
350
|
+
reporter.failure(skillName, writeErr);
|
|
351
|
+
return { ok: false, exitCode: EXIT_PARTIAL_FAILURE, error: writeErr };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
reporter.success(skillName, relInstructions, result.warnings.length);
|
|
356
|
+
return { ok: true, exitCode: EXIT_SUCCESS };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// =============================================================================
|
|
360
|
+
// BATCH EXPORT
|
|
361
|
+
// =============================================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Validate the --tier value. Returns { ok, normalizedTier, exitCode, message }.
|
|
365
|
+
*/
|
|
366
|
+
function validateTier(tierValue) {
|
|
367
|
+
if (tierValue === 'all') {
|
|
368
|
+
return { ok: true, normalizedTier: 'all' };
|
|
369
|
+
}
|
|
370
|
+
if (tierValue === '1' || tierValue === 'standalone') {
|
|
371
|
+
return { ok: true, normalizedTier: 'standalone' };
|
|
372
|
+
}
|
|
373
|
+
if (tierValue === '2' || tierValue === 'light-deps') {
|
|
374
|
+
return { ok: true, normalizedTier: 'light-deps' };
|
|
375
|
+
}
|
|
376
|
+
if (tierValue === '3' || tierValue === 'pipeline') {
|
|
377
|
+
return {
|
|
378
|
+
ok: false,
|
|
379
|
+
exitCode: EXIT_TIER_NOT_SUPPORTED,
|
|
380
|
+
message: 'Tier 3 skills are not exported per the portability schema.',
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
ok: false,
|
|
385
|
+
exitCode: EXIT_USAGE,
|
|
386
|
+
message: `Invalid --tier value: '${tierValue}'. Valid values: 1, 2, 3, standalone, light-deps, pipeline.`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function runBatch(tierValue, outputBase, dryRun, projectRoot, reporter) {
|
|
391
|
+
const tier = validateTier(tierValue);
|
|
392
|
+
if (!tier.ok) {
|
|
393
|
+
process.stderr.write(`${tier.message}\n`);
|
|
394
|
+
return tier.exitCode;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const manifestPath = path.join(projectRoot, '_bmad', '_config', 'skill-manifest.csv');
|
|
398
|
+
const { header, rows } = readManifest(manifestPath);
|
|
399
|
+
const nameIdx = header.indexOf('name');
|
|
400
|
+
const tierIdx = header.indexOf('tier');
|
|
401
|
+
|
|
402
|
+
// Manifest may contain the same skill name across multiple modules.
|
|
403
|
+
// Dedupe — each skill name is exported once.
|
|
404
|
+
// 'all' exports standalone + light-deps; specific tier exports that tier only
|
|
405
|
+
const tierFilter = tier.normalizedTier === 'all'
|
|
406
|
+
? (t) => t === 'standalone' || t === 'light-deps'
|
|
407
|
+
: (t) => t === tier.normalizedTier;
|
|
408
|
+
const matchingSkills = [
|
|
409
|
+
...new Set(rows.filter((r) => tierFilter(r[tierIdx])).map((r) => r[nameIdx])),
|
|
410
|
+
].sort();
|
|
411
|
+
|
|
412
|
+
if (matchingSkills.length === 0) {
|
|
413
|
+
process.stdout.write('Nothing to export — manifest matches found 0 skills\n');
|
|
414
|
+
return EXIT_SUCCESS;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (const skillName of matchingSkills) {
|
|
418
|
+
runSingle(skillName, outputBase, dryRun, projectRoot, reporter);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const counts = reporter.counts();
|
|
422
|
+
return counts.failed > 0 ? EXIT_PARTIAL_FAILURE : EXIT_SUCCESS;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// =============================================================================
|
|
426
|
+
// MAIN
|
|
427
|
+
// =============================================================================
|
|
428
|
+
|
|
429
|
+
function main() {
|
|
430
|
+
const argv = process.argv.slice(2);
|
|
431
|
+
|
|
432
|
+
// No args = print help and exit 0
|
|
433
|
+
if (argv.length === 0) {
|
|
434
|
+
printHelp();
|
|
435
|
+
return EXIT_SUCCESS;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const opts = parseArgs(argv);
|
|
439
|
+
|
|
440
|
+
if (opts.help) {
|
|
441
|
+
printHelp();
|
|
442
|
+
return EXIT_SUCCESS;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (opts.unknown) {
|
|
446
|
+
process.stderr.write(`Unknown flag: ${opts.unknown}. Run --help for usage.\n`);
|
|
447
|
+
return EXIT_USAGE;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Conflict matrix
|
|
451
|
+
const hasPositional = opts.positional.length > 0;
|
|
452
|
+
const hasTier = opts.tier !== null;
|
|
453
|
+
const hasAll = opts.all;
|
|
454
|
+
if (hasPositional && (hasTier || hasAll)) {
|
|
455
|
+
process.stderr.write(
|
|
456
|
+
'Conflict: positional skill name cannot be combined with --tier or --all. Run --help for usage.\n'
|
|
457
|
+
);
|
|
458
|
+
return EXIT_USAGE;
|
|
459
|
+
}
|
|
460
|
+
if (hasTier && hasAll) {
|
|
461
|
+
process.stderr.write('Conflict: --tier and --all cannot be combined. Run --help for usage.\n');
|
|
462
|
+
return EXIT_USAGE;
|
|
463
|
+
}
|
|
464
|
+
if (!hasPositional && !hasTier && !hasAll) {
|
|
465
|
+
process.stderr.write('No skill or batch flag provided. Run --help for usage.\n');
|
|
466
|
+
return EXIT_USAGE;
|
|
467
|
+
}
|
|
468
|
+
if (opts.positional.length > 1) {
|
|
469
|
+
process.stderr.write(
|
|
470
|
+
`Conflict: only one positional skill name allowed (got ${opts.positional.length}). Run --help for usage.\n`
|
|
471
|
+
);
|
|
472
|
+
return EXIT_USAGE;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const projectRoot = findProjectRoot();
|
|
476
|
+
|
|
477
|
+
// Resolve output base
|
|
478
|
+
let outputBase;
|
|
479
|
+
if (opts.output) {
|
|
480
|
+
outputBase = path.isAbsolute(opts.output)
|
|
481
|
+
? opts.output
|
|
482
|
+
: path.resolve(process.cwd(), opts.output);
|
|
483
|
+
} else {
|
|
484
|
+
outputBase = path.join(projectRoot, 'exported-skills');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const reporter = makeReporter();
|
|
488
|
+
|
|
489
|
+
let exitCode;
|
|
490
|
+
if (hasPositional) {
|
|
491
|
+
const result = runSingle(opts.positional[0], outputBase, opts.dryRun, projectRoot, reporter);
|
|
492
|
+
reporter.summary(opts.dryRun);
|
|
493
|
+
exitCode = result.exitCode;
|
|
494
|
+
} else {
|
|
495
|
+
// Batch: --tier or --all
|
|
496
|
+
const tierValue = hasAll ? 'all' : opts.tier;
|
|
497
|
+
exitCode = runBatch(tierValue, outputBase, opts.dryRun, projectRoot, reporter);
|
|
498
|
+
if (exitCode !== EXIT_TIER_NOT_SUPPORTED && exitCode !== EXIT_USAGE) {
|
|
499
|
+
reporter.summary(opts.dryRun);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return exitCode;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (require.main === module) {
|
|
507
|
+
process.exit(main());
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
module.exports = {
|
|
511
|
+
parseArgs,
|
|
512
|
+
validateTier,
|
|
513
|
+
buildReadme,
|
|
514
|
+
runSingle,
|
|
515
|
+
runBatch,
|
|
516
|
+
main,
|
|
517
|
+
EXIT_SUCCESS,
|
|
518
|
+
EXIT_USAGE,
|
|
519
|
+
EXIT_NOT_FOUND,
|
|
520
|
+
EXIT_TIER_NOT_SUPPORTED,
|
|
521
|
+
EXIT_PARTIAL_FAILURE,
|
|
522
|
+
};
|