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.
Files changed (78) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +37 -10
  3. package/_bmad/bme/_artifacts/config.yaml +15 -0
  4. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
  5. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
  6. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
  7. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
  8. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
  9. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
  10. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
  11. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
  12. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
  13. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
  14. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
  15. package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
  16. package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
  17. package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
  18. package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
  19. package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
  20. package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
  21. package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
  22. package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
  23. package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
  24. package/_bmad/bme/_team-factory/config.yaml +13 -0
  25. package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
  26. package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
  27. package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
  28. package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
  29. package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
  30. package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
  31. package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
  32. package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
  33. package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
  34. package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
  35. package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
  36. package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
  37. package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
  38. package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
  39. package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
  40. package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
  41. package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
  42. package/_bmad/bme/_team-factory/module-help.csv +3 -0
  43. package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
  44. package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
  45. package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
  46. package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
  47. package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
  48. package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
  49. package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
  50. package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
  51. package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
  52. package/_bmad/bme/_vortex/config.yaml +4 -4
  53. package/package.json +13 -7
  54. package/scripts/convoke-doctor.js +172 -1
  55. package/scripts/install-gyre-agents.js +0 -0
  56. package/scripts/lib/artifact-utils.js +521 -13
  57. package/scripts/lib/portfolio/portfolio-engine.js +301 -34
  58. package/scripts/lib/portfolio/rules/artifact-chain-rule.js +33 -3
  59. package/scripts/lib/portfolio/rules/conflict-resolver.js +22 -0
  60. package/scripts/migrate-artifacts.js +69 -10
  61. package/scripts/portability/catalog-generator.js +353 -0
  62. package/scripts/portability/classify-skills.js +646 -0
  63. package/scripts/portability/convoke-export.js +522 -0
  64. package/scripts/portability/export-engine.js +1156 -0
  65. package/scripts/portability/generate-adapters.js +79 -0
  66. package/scripts/portability/manifest-csv.js +147 -0
  67. package/scripts/portability/seed-catalog-repo.js +427 -0
  68. package/scripts/portability/templates/canonical-example.md +102 -0
  69. package/scripts/portability/templates/canonical-format.md +218 -0
  70. package/scripts/portability/templates/readme-template.md +72 -0
  71. package/scripts/portability/test-constants.js +42 -0
  72. package/scripts/portability/validate-classification.js +529 -0
  73. package/scripts/portability/validate-exports.js +348 -0
  74. package/scripts/update/lib/agent-registry.js +35 -0
  75. package/scripts/update/lib/config-merger.js +140 -10
  76. package/scripts/update/lib/refresh-installation.js +293 -8
  77. package/scripts/update/lib/utils.js +27 -1
  78. package/scripts/update/lib/validator.js +114 -4
@@ -0,0 +1,409 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const { toKebab, deriveWorkflowName } = require('../utils/naming-utils');
7
+
8
+ /** @typedef {import('../types/factory-types')} Types */
9
+
10
+ /**
11
+ * Write a new module block to agent-registry.js with Full Write Safety Protocol.
12
+ *
13
+ * Protocol: stage → validate → check → apply → verify → rollback
14
+ *
15
+ * This is the ONLY writer that uses the full protocol because agent-registry.js
16
+ * is a shared file consumed by refresh-installation, validator, convoke-doctor,
17
+ * installer, index.js, and migration-runner.
18
+ *
19
+ * @param {Object} specData - Parsed team spec with agents enriched with persona fields
20
+ * @param {string} registryPath - Absolute path to agent-registry.js
21
+ * @param {Object} [options]
22
+ * @param {boolean} [options.skipDirtyCheck] - Skip git dirty-tree detection (for tests)
23
+ * @returns {Promise<import('../types/factory-types').RegistryResult>}
24
+ */
25
+ async function writeRegistryBlock(specData, registryPath, options = {}) {
26
+ if (!specData.team_name_kebab || !specData.team_name_kebab.trim()) {
27
+ return { success: false, written: [], skipped: [], errors: ['team_name_kebab is required and must not be empty'], rollbackApplied: false };
28
+ }
29
+
30
+ const prefix = derivePrefix(specData.team_name_kebab);
31
+ const teamName = specData.team_name || specData.team_name_kebab;
32
+
33
+ // --- Idempotency check ---
34
+ let currentContent;
35
+ try {
36
+ currentContent = await fs.readFile(registryPath, 'utf8');
37
+ } catch (err) {
38
+ return { success: false, written: [], skipped: [], errors: [`Cannot read registry file: ${err.message}`], rollbackApplied: false };
39
+ }
40
+
41
+ if (currentContent.includes(`const ${prefix}_AGENTS`)) {
42
+ return { success: true, written: [], skipped: ['block already exists'], errors: [], rollbackApplied: false };
43
+ }
44
+
45
+ // --- 1. STAGE: Build module block + export additions ---
46
+ const workflowNames = buildWorkflowNames(specData);
47
+ const moduleBlock = buildModuleBlock(specData, prefix, teamName, workflowNames);
48
+ const exportNames = buildExportNames(prefix);
49
+
50
+ // --- 2. VALIDATE: Syntax, prefix uniqueness, additive-only ---
51
+ const validateErrors = validateStaged(moduleBlock, prefix, currentContent);
52
+ if (validateErrors.length > 0) {
53
+ return { success: false, written: [], skipped: [], errors: validateErrors, rollbackApplied: false };
54
+ }
55
+
56
+ // Validate staged block syntax via temp file
57
+ const syntaxError = await validateSyntax(moduleBlock, prefix);
58
+ if (syntaxError) {
59
+ return { success: false, written: [], skipped: [], errors: [syntaxError], rollbackApplied: false };
60
+ }
61
+
62
+ // --- 3. CHECK: Dirty-tree detection ---
63
+ if (!options.skipDirtyCheck) {
64
+ const dirtyResult = checkDirtyTree(registryPath);
65
+ if (dirtyResult.dirty) {
66
+ return { success: false, written: [], skipped: [], errors: [], rollbackApplied: false, dirty: true, diff: dirtyResult.diff };
67
+ }
68
+ }
69
+
70
+ // --- 4. APPLY: Read → save .bak → insert → write ---
71
+ const bakPath = `${registryPath}.bak`;
72
+ if (await fs.pathExists(bakPath)) {
73
+ return { success: false, written: [], skipped: [], errors: ['Stale .bak file exists — a previous run may have crashed. Remove it manually before retrying.'], rollbackApplied: false };
74
+ }
75
+ try {
76
+ await fs.writeFile(bakPath, currentContent, 'utf8');
77
+ } catch (err) {
78
+ return { success: false, written: [], skipped: [], errors: [`Failed to create backup: ${err.message}`], rollbackApplied: false };
79
+ }
80
+
81
+ let modified;
82
+ try {
83
+ modified = applyInsertions(currentContent, moduleBlock, exportNames);
84
+ } catch (err) {
85
+ await fs.remove(bakPath);
86
+ return { success: false, written: [], skipped: [], errors: [`Insertion failed: ${err.message}`], rollbackApplied: false };
87
+ }
88
+
89
+ try {
90
+ await fs.writeFile(registryPath, modified, 'utf8');
91
+ } catch (err) {
92
+ // Restore from backup
93
+ await fs.writeFile(registryPath, currentContent, 'utf8');
94
+ await fs.remove(bakPath);
95
+ return { success: false, written: [], skipped: [], errors: [`Write failed: ${err.message}`], rollbackApplied: true };
96
+ }
97
+
98
+ // --- 5. VERIFY: Re-read + node require() ---
99
+ const verifyError = verifyRequire(registryPath);
100
+ if (verifyError) {
101
+ // Rollback
102
+ await fs.writeFile(registryPath, currentContent, 'utf8');
103
+ await fs.remove(bakPath);
104
+ return { success: false, written: [], skipped: [], errors: [verifyError], rollbackApplied: true };
105
+ }
106
+
107
+ // --- Cleanup: Remove .bak ---
108
+ await fs.remove(bakPath);
109
+
110
+ return {
111
+ success: true,
112
+ written: [`${prefix}_AGENTS`, `${prefix}_WORKFLOWS`, `${prefix}_AGENT_FILES`, `${prefix}_AGENT_IDS`, `${prefix}_WORKFLOW_NAMES`],
113
+ skipped: [],
114
+ errors: [],
115
+ rollbackApplied: false
116
+ };
117
+ }
118
+
119
+ // ── Helpers ──────────────────────────────────────────────────────────
120
+
121
+ /**
122
+ * Derive SCREAMING_SNAKE_CASE prefix from team name kebab.
123
+ * @param {string} teamNameKebab - e.g. "test-team"
124
+ * @returns {string} - e.g. "TEST_TEAM"
125
+ */
126
+ function derivePrefix(teamNameKebab) {
127
+ return (teamNameKebab || '').replace(/^_/, '').replace(/-/g, '_').toUpperCase();
128
+ }
129
+
130
+ /**
131
+ * Build workflow names from spec data using shared deriveWorkflowName().
132
+ * @param {Object} specData
133
+ * @returns {Object} Map of agent_id → workflow_name
134
+ */
135
+ function buildWorkflowNames(specData) {
136
+ const map = {};
137
+ for (const agent of (specData.agents || [])) {
138
+ map[agent.id] = deriveWorkflowName(agent, specData);
139
+ }
140
+ return map;
141
+ }
142
+
143
+ /**
144
+ * Escape single quotes in a string for JS string literal output.
145
+ * @param {string} str
146
+ * @returns {string}
147
+ */
148
+ function escapeSingleQuotes(str) {
149
+ if (!str) return '';
150
+ return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r');
151
+ }
152
+
153
+ /**
154
+ * Build a registry agent entry object from enriched agent spec data.
155
+ * @param {Object} agentSpec - Agent spec with persona fields
156
+ * @param {string} teamNameKebab - Team name for stream field
157
+ * @returns {Object}
158
+ */
159
+ function buildAgentEntry(agentSpec, teamNameKebab) {
160
+ return {
161
+ id: agentSpec.id,
162
+ name: agentSpec.name || agentSpec.id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
163
+ icon: agentSpec.icon || '\u{2699}',
164
+ title: agentSpec.role || agentSpec.title || agentSpec.id,
165
+ stream: teamNameKebab,
166
+ persona: {
167
+ role: agentSpec.persona?.role || agentSpec.role || '',
168
+ identity: agentSpec.persona?.identity || '',
169
+ communication_style: agentSpec.persona?.communication_style || '',
170
+ expertise: agentSpec.persona?.expertise || '',
171
+ }
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Build the module block as a JS string.
177
+ * @param {Object} specData
178
+ * @param {string} prefix - SCREAMING_SNAKE_CASE
179
+ * @param {string} teamName - Display name
180
+ * @param {Object} workflowNames - Map of agent_id → workflow_name
181
+ * @returns {string}
182
+ */
183
+ function buildModuleBlock(specData, prefix, teamName, workflowNames) {
184
+ const agents = (specData.agents || []).map(a => buildAgentEntry(a, specData.team_name_kebab));
185
+ const workflows = [];
186
+ for (const agent of (specData.agents || [])) {
187
+ const wfName = workflowNames[agent.id];
188
+ if (wfName) {
189
+ workflows.push({ name: wfName, agent: agent.id });
190
+ }
191
+ }
192
+
193
+ const lines = [];
194
+
195
+ // Section comment (padded to ~72 chars like Gyre)
196
+ const label = `── ${teamName} Module `;
197
+ const pad = Math.max(0, 72 - 3 - label.length);
198
+ lines.push(`// ${label}${'─'.repeat(pad)}`);
199
+
200
+ // AGENTS array
201
+ lines.push(`const ${prefix}_AGENTS = [`);
202
+ for (const agent of agents) {
203
+ lines.push(' {');
204
+ lines.push(` id: '${escapeSingleQuotes(agent.id)}', name: '${escapeSingleQuotes(agent.name)}', icon: '${agent.icon}',`);
205
+ lines.push(` title: '${escapeSingleQuotes(agent.title)}', stream: '${escapeSingleQuotes(agent.stream)}',`);
206
+ lines.push(' persona: {');
207
+ lines.push(` role: '${escapeSingleQuotes(agent.persona.role)}',`);
208
+ lines.push(` identity: '${escapeSingleQuotes(agent.persona.identity)}',`);
209
+ lines.push(` communication_style: '${escapeSingleQuotes(agent.persona.communication_style)}',`);
210
+ lines.push(` expertise: '${escapeSingleQuotes(agent.persona.expertise)}',`);
211
+ lines.push(' },');
212
+ lines.push(' },');
213
+ }
214
+ lines.push('];');
215
+ lines.push('');
216
+
217
+ // WORKFLOWS array
218
+ lines.push(`const ${prefix}_WORKFLOWS = [`);
219
+ for (const wf of workflows) {
220
+ lines.push(` { name: '${escapeSingleQuotes(wf.name)}', agent: '${escapeSingleQuotes(wf.agent)}' },`);
221
+ }
222
+ lines.push('];');
223
+ lines.push('');
224
+
225
+ // Derived lists
226
+ lines.push(`// Derived lists for ${teamName}`);
227
+ lines.push(`const ${prefix}_AGENT_FILES = ${prefix}_AGENTS.map(a => \`\${a.id}.md\`);`);
228
+ lines.push(`const ${prefix}_AGENT_IDS = ${prefix}_AGENTS.map(a => a.id);`);
229
+ lines.push(`const ${prefix}_WORKFLOW_NAMES = ${prefix}_WORKFLOWS.map(w => w.name);`);
230
+
231
+ return lines.join('\n');
232
+ }
233
+
234
+ /**
235
+ * Build the list of export names to add to module.exports.
236
+ * @param {string} prefix
237
+ * @returns {string[]}
238
+ */
239
+ function buildExportNames(prefix) {
240
+ return [
241
+ `${prefix}_AGENTS`,
242
+ `${prefix}_WORKFLOWS`,
243
+ `${prefix}_AGENT_FILES`,
244
+ `${prefix}_AGENT_IDS`,
245
+ `${prefix}_WORKFLOW_NAMES`,
246
+ ];
247
+ }
248
+
249
+ /**
250
+ * Validate staged content: prefix uniqueness, additive-only.
251
+ * @param {string} moduleBlock
252
+ * @param {string} prefix
253
+ * @param {string} currentContent
254
+ * @returns {string[]} errors
255
+ */
256
+ function validateStaged(moduleBlock, prefix, currentContent) {
257
+ const errors = [];
258
+
259
+ // Check prefix collision
260
+ if (currentContent.includes(`const ${prefix}_AGENTS`)) {
261
+ errors.push(`Prefix collision: ${prefix}_AGENTS already exists in registry`);
262
+ }
263
+
264
+ // Check additive-only — no reassignment to existing variables
265
+ const existingConsts = [...currentContent.matchAll(/const\s+(\w+)\s*=/g)].map(m => m[1]);
266
+ const newConsts = [...moduleBlock.matchAll(/const\s+(\w+)\s*=/g)].map(m => m[1]);
267
+ for (const nc of newConsts) {
268
+ if (existingConsts.includes(nc)) {
269
+ errors.push(`Additive-only violation: ${nc} already exists`);
270
+ }
271
+ }
272
+
273
+ return errors;
274
+ }
275
+
276
+ /**
277
+ * Validate staged block syntax by writing to temp file and require()ing it.
278
+ * @param {string} moduleBlock
279
+ * @param {string} prefix
280
+ * @returns {Promise<string|null>} error message or null
281
+ */
282
+ async function validateSyntax(moduleBlock, prefix) {
283
+ const tmpDir = await fs.mkdtemp(path.join(require('os').tmpdir(), 'bmad-tf-validate-'));
284
+ const tmpFile = path.join(tmpDir, 'validate-block.js');
285
+ try {
286
+ // Wrap the block in a module so require() can parse it
287
+ const wrapped = `'use strict';\n${moduleBlock}\nmodule.exports = { ${buildExportNames(prefix).join(', ')} };\n`;
288
+ await fs.writeFile(tmpFile, wrapped, 'utf8');
289
+ execSync(`node -e "require('${tmpFile.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')"`, { timeout: 5000, stdio: 'pipe' });
290
+ return null;
291
+ } catch (err) {
292
+ return `Staged block syntax validation failed: ${err.stderr ? err.stderr.toString().trim() : err.message}`;
293
+ } finally {
294
+ await fs.remove(tmpDir);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Check if the registry file has uncommitted changes.
300
+ * @param {string} registryPath
301
+ * @returns {{ dirty: boolean, diff: string }}
302
+ */
303
+ function checkDirtyTree(registryPath) {
304
+ try {
305
+ const cwd = path.dirname(registryPath);
306
+ const unstaged = execSync(`git diff --name-only -- "${registryPath}"`, { cwd, timeout: 5000, stdio: 'pipe' }).toString().trim();
307
+ const staged = execSync(`git diff --cached --name-only -- "${registryPath}"`, { cwd, timeout: 5000, stdio: 'pipe' }).toString().trim();
308
+ const allDiffs = [unstaged, staged].filter(Boolean).join('\n');
309
+ return { dirty: allDiffs.length > 0, diff: allDiffs };
310
+ } catch {
311
+ // If git is not available or file is not in a repo, proceed
312
+ return { dirty: false, diff: '' };
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Insert module block and export names into registry content.
318
+ * @param {string} content - Current file content
319
+ * @param {string} moduleBlock - New module block to insert
320
+ * @param {string[]} exportNames - Export names to add
321
+ * @returns {string} Modified content
322
+ */
323
+ function applyInsertions(content, moduleBlock, exportNames) {
324
+ // Insert module block before `module.exports = {`
325
+ const exportsMarker = 'module.exports = {';
326
+ const markerIdx = content.indexOf(exportsMarker);
327
+ if (markerIdx === -1) {
328
+ throw new Error('Cannot find module.exports = { marker in registry file');
329
+ }
330
+
331
+ const before = content.slice(0, markerIdx);
332
+ const after = content.slice(markerIdx);
333
+
334
+ const withBlock = before + moduleBlock + '\n\n' + after;
335
+
336
+ // Insert export names before the closing `};` of module.exports
337
+ const closingIdx = withBlock.lastIndexOf('};');
338
+ if (closingIdx === -1) {
339
+ throw new Error('Cannot find closing }; of module.exports');
340
+ }
341
+
342
+ const beforeClosing = withBlock.slice(0, closingIdx);
343
+ const afterClosing = withBlock.slice(closingIdx);
344
+
345
+ const exportLines = exportNames.map(name => ` ${name},`).join('\n');
346
+ const withExports = beforeClosing + exportLines + '\n' + afterClosing;
347
+
348
+ return withExports;
349
+ }
350
+
351
+ /**
352
+ * Verify the written file can be require()d by Node.
353
+ * @param {string} registryPath
354
+ * @returns {string|null} error message or null
355
+ */
356
+ function verifyRequire(registryPath) {
357
+ try {
358
+ const absPath = path.resolve(registryPath);
359
+ execSync(`node -e "require('${absPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')"`, { timeout: 5000, stdio: 'pipe' });
360
+ return null;
361
+ } catch (err) {
362
+ return `Post-write require() verification failed: ${err.stderr ? err.stderr.toString().trim() : err.message}`;
363
+ }
364
+ }
365
+
366
+ // --- CLI entry point ---
367
+ if (require.main === module) {
368
+ const args = process.argv.slice(2);
369
+ const specFileIdx = args.indexOf('--spec-file');
370
+ const registryPathIdx = args.indexOf('--registry-path');
371
+
372
+ if (specFileIdx === -1 || !args[specFileIdx + 1]) {
373
+ console.error('Usage: node registry-writer.js --spec-file <path> [--registry-path <path>]');
374
+ process.exit(1);
375
+ }
376
+
377
+ const specFilePath = args[specFileIdx + 1];
378
+ const registryPath = registryPathIdx !== -1 && args[registryPathIdx + 1]
379
+ ? path.resolve(args[registryPathIdx + 1])
380
+ : path.resolve(__dirname, '../../../../../scripts/update/lib/agent-registry.js');
381
+
382
+ (async () => {
383
+ try {
384
+ const yaml = require('js-yaml');
385
+ const specContent = await fs.readFile(specFilePath, 'utf8');
386
+ const specData = yaml.load(specContent);
387
+
388
+ const result = await writeRegistryBlock(specData, registryPath);
389
+ console.log(JSON.stringify(result, null, 2));
390
+ process.exit(result.success ? 0 : 1);
391
+ } catch (err) {
392
+ console.log(JSON.stringify({ success: false, errors: [err.message] }, null, 2));
393
+ process.exit(1);
394
+ }
395
+ })();
396
+ }
397
+
398
+ module.exports = {
399
+ writeRegistryBlock,
400
+ derivePrefix,
401
+ buildAgentEntry,
402
+ buildModuleBlock,
403
+ buildExportNames,
404
+ buildWorkflowNames,
405
+ applyInsertions,
406
+ checkDirtyTree,
407
+ verifyRequire,
408
+ escapeSingleQuotes,
409
+ };
@@ -0,0 +1,3 @@
1
+ module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
2
+ _team-factory,solutioning,Create Team,CT,1,_bmad/bme/_team-factory/workflows/step-00-route.md,,optional,team-factory,,Create a new BMAD-compliant team through guided architectural discovery,_bmad-output/planning-artifacts,team-spec-{name}.yaml,
3
+ _team-factory,solutioning,Validate Team,VT,2,_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md,,optional,team-factory,,Run end-to-end validation on an existing team module,_bmad-output/planning-artifacts,validation-report.md,
@@ -0,0 +1,147 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Team Spec — Independent Pattern",
4
+ "description": "Schema for Independent composition pattern team specs. Agents operate standalone with no handoff contracts.",
5
+ "type": "object",
6
+ "required": [
7
+ "schema_version",
8
+ "team_name",
9
+ "team_name_kebab",
10
+ "composition_pattern",
11
+ "agents",
12
+ "integration"
13
+ ],
14
+ "additionalProperties": true,
15
+ "properties": {
16
+ "schema_version": {
17
+ "type": "string",
18
+ "pattern": "^\\d+\\.\\d+$",
19
+ "description": "Schema version (e.g., '1.0')"
20
+ },
21
+ "team_name": {
22
+ "type": "string",
23
+ "minLength": 1,
24
+ "description": "Human-readable team name"
25
+ },
26
+ "team_name_kebab": {
27
+ "type": "string",
28
+ "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
29
+ "description": "Kebab-case team name for directory naming"
30
+ },
31
+ "description": {
32
+ "type": "string",
33
+ "description": "What the team does"
34
+ },
35
+ "composition_pattern": {
36
+ "type": "string",
37
+ "enum": ["Independent"],
38
+ "description": "Must be 'Independent' for this schema"
39
+ },
40
+ "created": {
41
+ "type": "string",
42
+ "description": "ISO date string"
43
+ },
44
+ "factory_version": {
45
+ "type": "string",
46
+ "description": "Factory version that created this spec"
47
+ },
48
+ "agents": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "items": {
52
+ "type": "object",
53
+ "required": ["id", "role"],
54
+ "properties": {
55
+ "id": {
56
+ "type": "string",
57
+ "pattern": "^[a-z]+(-[a-z]+)*$",
58
+ "description": "Agent ID in kebab-case"
59
+ },
60
+ "name": {
61
+ "type": "string",
62
+ "description": "Display name (first name persona)"
63
+ },
64
+ "icon": {
65
+ "type": "string",
66
+ "description": "Single emoji"
67
+ },
68
+ "role": {
69
+ "type": "string",
70
+ "minLength": 1,
71
+ "description": "What this agent does"
72
+ },
73
+ "title": {
74
+ "type": "string",
75
+ "description": "Formal title"
76
+ },
77
+ "capabilities": {
78
+ "type": "array",
79
+ "items": { "type": "string" }
80
+ },
81
+ "overlap_acknowledgments": {
82
+ "type": "array",
83
+ "items": { "type": "string" }
84
+ },
85
+ "persona": {
86
+ "type": "object",
87
+ "properties": {
88
+ "role": { "type": "string" },
89
+ "identity": { "type": "string" },
90
+ "communication_style": { "type": "string" },
91
+ "expertise": { "type": "string" }
92
+ }
93
+ }
94
+ },
95
+ "additionalProperties": false
96
+ }
97
+ },
98
+ "integration": {
99
+ "type": "object",
100
+ "required": ["output_directory"],
101
+ "properties": {
102
+ "output_directory": {
103
+ "type": "string",
104
+ "minLength": 1,
105
+ "description": "Where output artifacts are saved"
106
+ },
107
+ "compass_routing": {
108
+ "type": "string",
109
+ "enum": ["optional", "per-agent"],
110
+ "description": "Compass routing mode (optional for Independent)"
111
+ }
112
+ },
113
+ "additionalProperties": false
114
+ },
115
+ "progress": {
116
+ "type": "object",
117
+ "properties": {
118
+ "orient": { "type": "string" },
119
+ "scope": { "type": "string" },
120
+ "connect": { "type": "string" },
121
+ "review": { "type": "string" },
122
+ "generate": {},
123
+ "validate": { "type": "string" }
124
+ }
125
+ },
126
+ "decisions": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "object",
130
+ "properties": {
131
+ "step": { "type": "string" },
132
+ "decision": { "type": "string" },
133
+ "default_accepted": { "type": "boolean" },
134
+ "rationale": { "type": "string" }
135
+ }
136
+ }
137
+ },
138
+ "metrics": {
139
+ "type": "object",
140
+ "properties": {
141
+ "discovery_path": {},
142
+ "hardest_step": {},
143
+ "would_use_again": {}
144
+ }
145
+ }
146
+ }
147
+ }