bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.23.6fbcf839
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/.github/workflows/publish-multi-artifact.yaml +6 -2
- package/eslint.config.mjs +2 -2
- package/package.json +2 -2
- package/src/bmm/module.yaml +2 -2
- package/src/core/lib/scope/artifact-resolver.js +26 -26
- package/src/core/lib/scope/event-logger.js +34 -45
- package/src/core/lib/scope/index.js +3 -3
- package/src/core/lib/scope/scope-context.js +22 -28
- package/src/core/lib/scope/scope-initializer.js +29 -31
- package/src/core/lib/scope/scope-manager.js +21 -21
- package/src/core/lib/scope/scope-migrator.js +44 -52
- package/src/core/lib/scope/scope-sync.js +42 -48
- package/src/core/lib/scope/scope-validator.js +16 -21
- package/src/core/lib/scope/state-lock.js +37 -43
- package/src/core/module.yaml +2 -2
- package/test/test-scope-e2e.js +65 -76
- package/test/test-scope-system.js +66 -72
- package/tools/cli/commands/scope.js +73 -73
- package/tools/cli/installers/lib/modules/manager.js +2 -2
- package/tools/cli/scripts/migrate-workflows.js +43 -51
|
@@ -32,30 +32,30 @@ function displayScopeList(scopes) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
console.log(chalk.bold('\n Scopes:\n'));
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
// Calculate column widths
|
|
37
|
-
const idWidth = Math.max(10, ...scopes.map(s => s.id.length)) + 2;
|
|
38
|
-
const nameWidth = Math.max(15, ...scopes.map(s => (s.name || '').length)) + 2;
|
|
39
|
-
|
|
37
|
+
const idWidth = Math.max(10, ...scopes.map((s) => s.id.length)) + 2;
|
|
38
|
+
const nameWidth = Math.max(15, ...scopes.map((s) => (s.name || '').length)) + 2;
|
|
39
|
+
|
|
40
40
|
// Header
|
|
41
41
|
console.log(
|
|
42
42
|
chalk.dim(' ') +
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
chalk.bold('ID'.padEnd(idWidth)) +
|
|
44
|
+
chalk.bold('Name'.padEnd(nameWidth)) +
|
|
45
|
+
chalk.bold('Status'.padEnd(10)) +
|
|
46
|
+
chalk.bold('Created'),
|
|
47
47
|
);
|
|
48
48
|
console.log(chalk.dim(' ' + '─'.repeat(idWidth + nameWidth + 10 + 20)));
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
// Rows
|
|
51
51
|
for (const scope of scopes) {
|
|
52
52
|
const statusColor = scope.status === 'active' ? chalk.green : chalk.dim;
|
|
53
53
|
console.log(
|
|
54
54
|
' ' +
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
chalk.cyan(scope.id.padEnd(idWidth)) +
|
|
56
|
+
(scope.name || scope.id).padEnd(nameWidth) +
|
|
57
|
+
statusColor(scope.status.padEnd(10)) +
|
|
58
|
+
chalk.dim(formatDate(scope.created).split(' ')[0]),
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
console.log();
|
|
@@ -69,7 +69,7 @@ function displayScopeList(scopes) {
|
|
|
69
69
|
*/
|
|
70
70
|
function displayScopeInfo(scope, paths, tree) {
|
|
71
71
|
console.log(chalk.bold(`\n Scope: ${scope.name || scope.id}\n`));
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
console.log(chalk.dim(' ─────────────────────────────────────'));
|
|
74
74
|
console.log(` ${chalk.bold('ID:')} ${chalk.cyan(scope.id)}`);
|
|
75
75
|
console.log(` ${chalk.bold('Name:')} ${scope.name || 'N/A'}`);
|
|
@@ -78,13 +78,13 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
78
78
|
console.log(` ${chalk.bold('Created:')} ${formatDate(scope.created)}`);
|
|
79
79
|
console.log(` ${chalk.bold('Last Active:')} ${formatDate(scope._meta?.last_activity)}`);
|
|
80
80
|
console.log(` ${chalk.bold('Artifacts:')} ${scope._meta?.artifact_count || 0}`);
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
console.log(chalk.dim('\n ─────────────────────────────────────'));
|
|
83
83
|
console.log(chalk.bold(' Paths:'));
|
|
84
84
|
console.log(` Planning: ${chalk.dim(paths.planning)}`);
|
|
85
85
|
console.log(` Implementation: ${chalk.dim(paths.implementation)}`);
|
|
86
86
|
console.log(` Tests: ${chalk.dim(paths.tests)}`);
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
console.log(chalk.dim('\n ─────────────────────────────────────'));
|
|
89
89
|
console.log(chalk.bold(' Dependencies:'));
|
|
90
90
|
if (tree.dependencies.length > 0) {
|
|
@@ -95,7 +95,7 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
95
95
|
} else {
|
|
96
96
|
console.log(chalk.dim(' None'));
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
console.log(chalk.bold('\n Dependents (scopes that depend on this):'));
|
|
100
100
|
if (tree.dependents.length > 0) {
|
|
101
101
|
for (const dep of tree.dependents) {
|
|
@@ -104,7 +104,7 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
104
104
|
} else {
|
|
105
105
|
console.log(chalk.dim(' None'));
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
console.log();
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -113,7 +113,7 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
113
113
|
*/
|
|
114
114
|
async function handleList(projectRoot, options) {
|
|
115
115
|
const manager = new ScopeManager({ projectRoot });
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
try {
|
|
118
118
|
await manager.initialize();
|
|
119
119
|
const scopes = await manager.listScopes(options.status ? { status: options.status } : {});
|
|
@@ -134,7 +134,7 @@ async function handleCreate(projectRoot, scopeId, options) {
|
|
|
134
134
|
const manager = new ScopeManager({ projectRoot });
|
|
135
135
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
136
136
|
const validator = new ScopeValidator();
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
// If no scopeId provided, prompt for it
|
|
139
139
|
if (!scopeId) {
|
|
140
140
|
const inputId = await text({
|
|
@@ -143,80 +143,80 @@ async function handleCreate(projectRoot, scopeId, options) {
|
|
|
143
143
|
validate: (value) => {
|
|
144
144
|
const result = validator.validateScopeId(value);
|
|
145
145
|
return result.valid ? undefined : result.error;
|
|
146
|
-
}
|
|
146
|
+
},
|
|
147
147
|
});
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
if (isCancel(inputId)) {
|
|
150
150
|
console.log(chalk.yellow('Cancelled.'));
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
scopeId = inputId;
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
|
|
156
156
|
// Validate scope ID
|
|
157
157
|
const idValidation = validator.validateScopeId(scopeId);
|
|
158
158
|
if (!idValidation.valid) {
|
|
159
159
|
console.error(chalk.red(`Error: ${idValidation.error}`));
|
|
160
160
|
process.exit(1);
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
// Get scope name if not provided
|
|
164
164
|
let name = options.name;
|
|
165
165
|
if (!name) {
|
|
166
166
|
const inputName = await text({
|
|
167
167
|
message: 'Enter scope name (human-readable):',
|
|
168
168
|
placeholder: `e.g., Authentication Service`,
|
|
169
|
-
initialValue: scopeId.charAt(0).toUpperCase() + scopeId.slice(1).replaceAll('-', ' ')
|
|
169
|
+
initialValue: scopeId.charAt(0).toUpperCase() + scopeId.slice(1).replaceAll('-', ' '),
|
|
170
170
|
});
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
if (isCancel(inputName)) {
|
|
173
173
|
console.log(chalk.yellow('Cancelled.'));
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
name = inputName;
|
|
177
177
|
}
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
// Get description if not provided
|
|
180
180
|
let description = options.description;
|
|
181
181
|
if (!description) {
|
|
182
182
|
const inputDesc = await text({
|
|
183
183
|
message: 'Enter scope description (optional):',
|
|
184
|
-
placeholder: 'Brief description of this scope'
|
|
184
|
+
placeholder: 'Brief description of this scope',
|
|
185
185
|
});
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
if (isCancel(inputDesc)) {
|
|
188
188
|
console.log(chalk.yellow('Cancelled.'));
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
description = inputDesc || '';
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
console.log(chalk.blue('\nCreating scope...'));
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
// Initialize scope system if needed
|
|
197
197
|
await manager.initialize();
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
// Check if system is initialized
|
|
200
200
|
const systemInit = await initializer.isSystemInitialized();
|
|
201
201
|
if (!systemInit) {
|
|
202
202
|
console.log(chalk.blue('Initializing scope system...'));
|
|
203
203
|
await initializer.initializeScopeSystem();
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
// Create scope in configuration
|
|
207
207
|
const scope = await manager.createScope(scopeId, {
|
|
208
208
|
name,
|
|
209
209
|
description,
|
|
210
|
-
dependencies: options.dependencies ? options.dependencies.split(',').map(d => d.trim()) : []
|
|
210
|
+
dependencies: options.dependencies ? options.dependencies.split(',').map((d) => d.trim()) : [],
|
|
211
211
|
});
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
// Create scope directory structure
|
|
214
214
|
const paths = await initializer.initializeScope(scopeId, {
|
|
215
215
|
name,
|
|
216
216
|
description,
|
|
217
|
-
createContext: options.context
|
|
217
|
+
createContext: options.context,
|
|
218
218
|
});
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' created successfully!\n`));
|
|
221
221
|
console.log(chalk.dim(' Directories created:'));
|
|
222
222
|
console.log(` ${paths.planning}`);
|
|
@@ -235,20 +235,20 @@ async function handleInfo(projectRoot, scopeId) {
|
|
|
235
235
|
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope info <scope-id>'));
|
|
236
236
|
process.exit(1);
|
|
237
237
|
}
|
|
238
|
-
|
|
238
|
+
|
|
239
239
|
const manager = new ScopeManager({ projectRoot });
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
await manager.initialize();
|
|
242
242
|
const scope = await manager.getScope(scopeId);
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
if (!scope) {
|
|
245
245
|
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
246
246
|
process.exit(1);
|
|
247
247
|
}
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
const paths = await manager.getScopePaths(scopeId);
|
|
250
250
|
const tree = await manager.getDependencyTree(scopeId);
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
displayScopeInfo(scope, paths, tree);
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -260,39 +260,39 @@ async function handleRemove(projectRoot, scopeId, options) {
|
|
|
260
260
|
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope remove <scope-id>'));
|
|
261
261
|
process.exit(1);
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
const manager = new ScopeManager({ projectRoot });
|
|
265
265
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
await manager.initialize();
|
|
268
|
-
|
|
268
|
+
|
|
269
269
|
const scope = await manager.getScope(scopeId);
|
|
270
270
|
if (!scope) {
|
|
271
271
|
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
272
272
|
process.exit(1);
|
|
273
273
|
}
|
|
274
|
-
|
|
274
|
+
|
|
275
275
|
// Confirm removal unless --force
|
|
276
276
|
if (!options.force) {
|
|
277
277
|
const confirmed = await confirm({
|
|
278
278
|
message: `Are you sure you want to remove scope '${scopeId}'? This will delete all scope artifacts!`,
|
|
279
|
-
initialValue: false
|
|
279
|
+
initialValue: false,
|
|
280
280
|
});
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
if (isCancel(confirmed) || !confirmed) {
|
|
283
283
|
console.log(chalk.yellow('Cancelled.'));
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
console.log(chalk.blue('\nRemoving scope...'));
|
|
289
|
-
|
|
289
|
+
|
|
290
290
|
// Remove scope directory (with backup)
|
|
291
291
|
await initializer.removeScope(scopeId, { backup: !options.noBackup });
|
|
292
|
-
|
|
292
|
+
|
|
293
293
|
// Remove from configuration
|
|
294
294
|
await manager.removeScope(scopeId, { force: true });
|
|
295
|
-
|
|
295
|
+
|
|
296
296
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' removed successfully!`));
|
|
297
297
|
if (!options.noBackup) {
|
|
298
298
|
console.log(chalk.dim(' A backup was created in _bmad-output/'));
|
|
@@ -306,12 +306,12 @@ async function handleRemove(projectRoot, scopeId, options) {
|
|
|
306
306
|
async function handleInit(projectRoot) {
|
|
307
307
|
const manager = new ScopeManager({ projectRoot });
|
|
308
308
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
console.log(chalk.blue('\nInitializing scope system...'));
|
|
311
|
-
|
|
311
|
+
|
|
312
312
|
await manager.initialize();
|
|
313
313
|
await initializer.initializeScopeSystem();
|
|
314
|
-
|
|
314
|
+
|
|
315
315
|
console.log(chalk.green('\n✓ Scope system initialized successfully!\n'));
|
|
316
316
|
console.log(chalk.dim(' Created:'));
|
|
317
317
|
console.log(` ${chalk.cyan('_bmad/_config/scopes.yaml')} - Scope configuration`);
|
|
@@ -330,12 +330,12 @@ async function handleArchive(projectRoot, scopeId) {
|
|
|
330
330
|
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope archive <scope-id>'));
|
|
331
331
|
process.exit(1);
|
|
332
332
|
}
|
|
333
|
-
|
|
333
|
+
|
|
334
334
|
const manager = new ScopeManager({ projectRoot });
|
|
335
|
-
|
|
335
|
+
|
|
336
336
|
await manager.initialize();
|
|
337
337
|
await manager.archiveScope(scopeId);
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' archived.\n`));
|
|
340
340
|
}
|
|
341
341
|
|
|
@@ -347,12 +347,12 @@ async function handleActivate(projectRoot, scopeId) {
|
|
|
347
347
|
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope activate <scope-id>'));
|
|
348
348
|
process.exit(1);
|
|
349
349
|
}
|
|
350
|
-
|
|
350
|
+
|
|
351
351
|
const manager = new ScopeManager({ projectRoot });
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
await manager.initialize();
|
|
354
354
|
await manager.activateScope(scopeId);
|
|
355
|
-
|
|
355
|
+
|
|
356
356
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' activated.\n`));
|
|
357
357
|
}
|
|
358
358
|
|
|
@@ -397,61 +397,61 @@ module.exports = {
|
|
|
397
397
|
['-f, --force', 'Force operation without confirmation'],
|
|
398
398
|
['--no-backup', 'Skip backup on remove'],
|
|
399
399
|
['--context', 'Create scope-specific project-context.md'],
|
|
400
|
-
['-s, --status <status>', 'Filter by status (active/archived)']
|
|
400
|
+
['-s, --status <status>', 'Filter by status (active/archived)'],
|
|
401
401
|
],
|
|
402
402
|
action: async (subcommand, id, options) => {
|
|
403
403
|
try {
|
|
404
404
|
// Determine project root
|
|
405
405
|
const projectRoot = process.cwd();
|
|
406
|
-
|
|
406
|
+
|
|
407
407
|
// Handle subcommands
|
|
408
408
|
switch (subcommand) {
|
|
409
409
|
case 'init': {
|
|
410
410
|
await handleInit(projectRoot);
|
|
411
411
|
break;
|
|
412
412
|
}
|
|
413
|
-
|
|
413
|
+
|
|
414
414
|
case 'list':
|
|
415
415
|
case 'ls': {
|
|
416
416
|
await handleList(projectRoot, options);
|
|
417
417
|
break;
|
|
418
418
|
}
|
|
419
|
-
|
|
419
|
+
|
|
420
420
|
case 'create':
|
|
421
421
|
case 'new': {
|
|
422
422
|
await handleCreate(projectRoot, id, options);
|
|
423
423
|
break;
|
|
424
424
|
}
|
|
425
|
-
|
|
425
|
+
|
|
426
426
|
case 'info':
|
|
427
427
|
case 'show': {
|
|
428
428
|
await handleInfo(projectRoot, id);
|
|
429
429
|
break;
|
|
430
430
|
}
|
|
431
|
-
|
|
431
|
+
|
|
432
432
|
case 'remove':
|
|
433
433
|
case 'rm':
|
|
434
434
|
case 'delete': {
|
|
435
435
|
await handleRemove(projectRoot, id, options);
|
|
436
436
|
break;
|
|
437
437
|
}
|
|
438
|
-
|
|
438
|
+
|
|
439
439
|
case 'archive': {
|
|
440
440
|
await handleArchive(projectRoot, id);
|
|
441
441
|
break;
|
|
442
442
|
}
|
|
443
|
-
|
|
443
|
+
|
|
444
444
|
case 'activate': {
|
|
445
445
|
await handleActivate(projectRoot, id);
|
|
446
446
|
break;
|
|
447
447
|
}
|
|
448
|
-
|
|
448
|
+
|
|
449
449
|
case 'help':
|
|
450
450
|
case undefined: {
|
|
451
451
|
showHelp();
|
|
452
452
|
break;
|
|
453
453
|
}
|
|
454
|
-
|
|
454
|
+
|
|
455
455
|
default: {
|
|
456
456
|
// If subcommand looks like an ID, show info for it
|
|
457
457
|
if (subcommand && !subcommand.startsWith('-')) {
|
|
@@ -461,7 +461,7 @@ module.exports = {
|
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
|
-
|
|
464
|
+
|
|
465
465
|
process.exit(0);
|
|
466
466
|
} catch (error) {
|
|
467
467
|
console.error(chalk.red(`\nError: ${error.message}`));
|
|
@@ -470,5 +470,5 @@ module.exports = {
|
|
|
470
470
|
}
|
|
471
471
|
process.exit(1);
|
|
472
472
|
}
|
|
473
|
-
}
|
|
473
|
+
},
|
|
474
474
|
};
|
|
@@ -416,7 +416,7 @@ class ModuleManager {
|
|
|
416
416
|
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
|
417
417
|
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
|
418
418
|
try {
|
|
419
|
-
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
|
419
|
+
execSync('npm install --production --ignore-scripts --no-audit --no-fund --prefer-offline --no-progress', {
|
|
420
420
|
cwd: moduleCacheDir,
|
|
421
421
|
stdio: 'pipe',
|
|
422
422
|
timeout: 120_000, // 2 minute timeout
|
|
@@ -441,7 +441,7 @@ class ModuleManager {
|
|
|
441
441
|
if (packageJsonNewer) {
|
|
442
442
|
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
|
443
443
|
try {
|
|
444
|
-
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
|
444
|
+
execSync('npm install --production --ignore-scripts --no-audit --no-fund --prefer-offline --no-progress', {
|
|
445
445
|
cwd: moduleCacheDir,
|
|
446
446
|
stdio: 'pipe',
|
|
447
447
|
timeout: 120_000, // 2 minute timeout
|