bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.23.02a963fa
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/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
|
};
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Workflow Migration Script
|
|
4
|
-
*
|
|
3
|
+
*
|
|
5
4
|
* Updates workflow.yaml files to support the multi-scope system.
|
|
6
5
|
* Primarily updates test_dir and other path variables to use scope-aware paths.
|
|
7
|
-
*
|
|
6
|
+
*
|
|
8
7
|
* Usage:
|
|
9
8
|
* node migrate-workflows.js [--dry-run] [--verbose]
|
|
10
|
-
*
|
|
9
|
+
*
|
|
11
10
|
* Options:
|
|
12
11
|
* --dry-run Show what would be changed without making changes
|
|
13
12
|
* --verbose Show detailed output
|
|
@@ -28,48 +27,42 @@ const PATH_MIGRATIONS = [
|
|
|
28
27
|
{
|
|
29
28
|
pattern: /\{output_folder\}\/tests/g,
|
|
30
29
|
replacement: '{scope_tests}',
|
|
31
|
-
description: 'test directory to scope_tests'
|
|
30
|
+
description: 'test directory to scope_tests',
|
|
32
31
|
},
|
|
33
32
|
{
|
|
34
33
|
pattern: /\{config_source:implementation_artifacts\}\/tests/g,
|
|
35
34
|
replacement: '{config_source:scope_tests}',
|
|
36
|
-
description: 'implementation_artifacts tests to scope_tests'
|
|
35
|
+
description: 'implementation_artifacts tests to scope_tests',
|
|
37
36
|
},
|
|
38
37
|
// Planning artifacts
|
|
39
38
|
{
|
|
40
39
|
pattern: /\{output_folder\}\/planning-artifacts/g,
|
|
41
40
|
replacement: '{config_source:planning_artifacts}',
|
|
42
|
-
description: 'output_folder planning to config_source'
|
|
41
|
+
description: 'output_folder planning to config_source',
|
|
43
42
|
},
|
|
44
|
-
// Implementation artifacts
|
|
43
|
+
// Implementation artifacts
|
|
45
44
|
{
|
|
46
45
|
pattern: /\{output_folder\}\/implementation-artifacts/g,
|
|
47
46
|
replacement: '{config_source:implementation_artifacts}',
|
|
48
|
-
description: 'output_folder implementation to config_source'
|
|
49
|
-
}
|
|
47
|
+
description: 'output_folder implementation to config_source',
|
|
48
|
+
},
|
|
50
49
|
];
|
|
51
50
|
|
|
52
51
|
// Variables that indicate scope requirement
|
|
53
|
-
const SCOPE_INDICATORS = [
|
|
54
|
-
'{scope}',
|
|
55
|
-
'{scope_path}',
|
|
56
|
-
'{scope_tests}',
|
|
57
|
-
'{scope_planning}',
|
|
58
|
-
'{scope_implementation}'
|
|
59
|
-
];
|
|
52
|
+
const SCOPE_INDICATORS = ['{scope}', '{scope_path}', '{scope_tests}', '{scope_planning}', '{scope_implementation}'];
|
|
60
53
|
|
|
61
54
|
/**
|
|
62
55
|
* Find all workflow.yaml files
|
|
63
56
|
*/
|
|
64
57
|
async function findWorkflowFiles(basePath) {
|
|
65
58
|
const files = [];
|
|
66
|
-
|
|
59
|
+
|
|
67
60
|
async function walk(dir) {
|
|
68
61
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
69
|
-
|
|
62
|
+
|
|
70
63
|
for (const entry of entries) {
|
|
71
64
|
const fullPath = path.join(dir, entry.name);
|
|
72
|
-
|
|
65
|
+
|
|
73
66
|
if (entry.isDirectory()) {
|
|
74
67
|
// Skip node_modules and hidden directories
|
|
75
68
|
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
@@ -80,7 +73,7 @@ async function findWorkflowFiles(basePath) {
|
|
|
80
73
|
}
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
|
-
|
|
76
|
+
|
|
84
77
|
await walk(basePath);
|
|
85
78
|
return files;
|
|
86
79
|
}
|
|
@@ -89,7 +82,7 @@ async function findWorkflowFiles(basePath) {
|
|
|
89
82
|
* Check if a workflow already uses scope variables
|
|
90
83
|
*/
|
|
91
84
|
function usesScope(content) {
|
|
92
|
-
return SCOPE_INDICATORS.some(indicator => content.includes(indicator));
|
|
85
|
+
return SCOPE_INDICATORS.some((indicator) => content.includes(indicator));
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
/**
|
|
@@ -101,20 +94,20 @@ function analyzeWorkflow(content, filePath) {
|
|
|
101
94
|
needsMigration: false,
|
|
102
95
|
alreadyScoped: false,
|
|
103
96
|
suggestions: [],
|
|
104
|
-
currentVariables: []
|
|
97
|
+
currentVariables: [],
|
|
105
98
|
};
|
|
106
|
-
|
|
99
|
+
|
|
107
100
|
// Check if already uses scope
|
|
108
101
|
if (usesScope(content)) {
|
|
109
102
|
analysis.alreadyScoped = true;
|
|
110
103
|
return analysis;
|
|
111
104
|
}
|
|
112
|
-
|
|
105
|
+
|
|
113
106
|
// Find variables that might need migration
|
|
114
107
|
const variablePattern = /\{[^}]+\}/g;
|
|
115
108
|
const matches = content.match(variablePattern) || [];
|
|
116
109
|
analysis.currentVariables = [...new Set(matches)];
|
|
117
|
-
|
|
110
|
+
|
|
118
111
|
// Check each migration pattern
|
|
119
112
|
for (const migration of PATH_MIGRATIONS) {
|
|
120
113
|
if (migration.pattern.test(content)) {
|
|
@@ -122,21 +115,21 @@ function analyzeWorkflow(content, filePath) {
|
|
|
122
115
|
analysis.suggestions.push({
|
|
123
116
|
description: migration.description,
|
|
124
117
|
pattern: migration.pattern.toString(),
|
|
125
|
-
replacement: migration.replacement
|
|
118
|
+
replacement: migration.replacement,
|
|
126
119
|
});
|
|
127
120
|
}
|
|
128
121
|
}
|
|
129
|
-
|
|
122
|
+
|
|
130
123
|
// Check for test_dir variable
|
|
131
124
|
if (content.includes('test_dir:') || content.includes('test_dir:')) {
|
|
132
125
|
analysis.needsMigration = true;
|
|
133
126
|
analysis.suggestions.push({
|
|
134
127
|
description: 'Has test_dir variable - may need scope_tests',
|
|
135
128
|
pattern: 'test_dir',
|
|
136
|
-
replacement: 'scope_tests via config_source'
|
|
129
|
+
replacement: 'scope_tests via config_source',
|
|
137
130
|
});
|
|
138
131
|
}
|
|
139
|
-
|
|
132
|
+
|
|
140
133
|
return analysis;
|
|
141
134
|
}
|
|
142
135
|
|
|
@@ -146,14 +139,14 @@ function analyzeWorkflow(content, filePath) {
|
|
|
146
139
|
function migrateWorkflow(content) {
|
|
147
140
|
let migrated = content;
|
|
148
141
|
let changes = [];
|
|
149
|
-
|
|
142
|
+
|
|
150
143
|
for (const migration of PATH_MIGRATIONS) {
|
|
151
144
|
if (migration.pattern.test(migrated)) {
|
|
152
145
|
migrated = migrated.replace(migration.pattern, migration.replacement);
|
|
153
146
|
changes.push(migration.description);
|
|
154
147
|
}
|
|
155
148
|
}
|
|
156
|
-
|
|
149
|
+
|
|
157
150
|
return { content: migrated, changes };
|
|
158
151
|
}
|
|
159
152
|
|
|
@@ -163,12 +156,12 @@ function migrateWorkflow(content) {
|
|
|
163
156
|
function addScopeMarker(content) {
|
|
164
157
|
try {
|
|
165
158
|
const parsed = yaml.parse(content);
|
|
166
|
-
|
|
159
|
+
|
|
167
160
|
// Add scope_required if not present
|
|
168
161
|
if (!parsed.scope_required) {
|
|
169
162
|
parsed.scope_required = false; // Default to false for backward compatibility
|
|
170
163
|
}
|
|
171
|
-
|
|
164
|
+
|
|
172
165
|
return yaml.stringify(parsed, { lineWidth: 120 });
|
|
173
166
|
} catch {
|
|
174
167
|
// If YAML parsing fails, return original
|
|
@@ -183,37 +176,37 @@ async function main() {
|
|
|
183
176
|
const args = new Set(process.argv.slice(2));
|
|
184
177
|
const dryRun = args.has('--dry-run');
|
|
185
178
|
const verbose = args.has('--verbose');
|
|
186
|
-
|
|
179
|
+
|
|
187
180
|
console.log(chalk.bold('\nWorkflow Migration Script'));
|
|
188
181
|
console.log(chalk.dim('Updating workflow.yaml files for multi-scope support\n'));
|
|
189
|
-
|
|
182
|
+
|
|
190
183
|
if (dryRun) {
|
|
191
184
|
console.log(chalk.yellow('DRY RUN MODE - No changes will be made\n'));
|
|
192
185
|
}
|
|
193
|
-
|
|
186
|
+
|
|
194
187
|
// Find all workflow files
|
|
195
188
|
console.log(chalk.blue('Scanning for workflow.yaml files...'));
|
|
196
189
|
const files = await findWorkflowFiles(SRC_PATH);
|
|
197
190
|
console.log(chalk.green(`Found ${files.length} workflow.yaml files\n`));
|
|
198
|
-
|
|
191
|
+
|
|
199
192
|
// Analysis results
|
|
200
193
|
const results = {
|
|
201
194
|
analyzed: 0,
|
|
202
195
|
alreadyScoped: 0,
|
|
203
196
|
migrated: 0,
|
|
204
197
|
noChanges: 0,
|
|
205
|
-
errors: []
|
|
198
|
+
errors: [],
|
|
206
199
|
};
|
|
207
|
-
|
|
200
|
+
|
|
208
201
|
// Process each file
|
|
209
202
|
for (const filePath of files) {
|
|
210
203
|
const relativePath = path.relative(SRC_PATH, filePath);
|
|
211
204
|
results.analyzed++;
|
|
212
|
-
|
|
205
|
+
|
|
213
206
|
try {
|
|
214
207
|
const content = await fs.readFile(filePath, 'utf8');
|
|
215
208
|
const analysis = analyzeWorkflow(content, filePath);
|
|
216
|
-
|
|
209
|
+
|
|
217
210
|
if (analysis.alreadyScoped) {
|
|
218
211
|
results.alreadyScoped++;
|
|
219
212
|
if (verbose) {
|
|
@@ -221,7 +214,7 @@ async function main() {
|
|
|
221
214
|
}
|
|
222
215
|
continue;
|
|
223
216
|
}
|
|
224
|
-
|
|
217
|
+
|
|
225
218
|
if (!analysis.needsMigration) {
|
|
226
219
|
results.noChanges++;
|
|
227
220
|
if (verbose) {
|
|
@@ -229,16 +222,16 @@ async function main() {
|
|
|
229
222
|
}
|
|
230
223
|
continue;
|
|
231
224
|
}
|
|
232
|
-
|
|
225
|
+
|
|
233
226
|
// Apply migration
|
|
234
227
|
const { content: migrated, changes } = migrateWorkflow(content);
|
|
235
|
-
|
|
228
|
+
|
|
236
229
|
if (changes.length > 0) {
|
|
237
230
|
console.log(chalk.cyan(` ● ${relativePath}`));
|
|
238
231
|
for (const change of changes) {
|
|
239
232
|
console.log(chalk.dim(` → ${change}`));
|
|
240
233
|
}
|
|
241
|
-
|
|
234
|
+
|
|
242
235
|
if (!dryRun) {
|
|
243
236
|
await fs.writeFile(filePath, migrated, 'utf8');
|
|
244
237
|
}
|
|
@@ -246,13 +239,12 @@ async function main() {
|
|
|
246
239
|
} else {
|
|
247
240
|
results.noChanges++;
|
|
248
241
|
}
|
|
249
|
-
|
|
250
242
|
} catch (error) {
|
|
251
243
|
results.errors.push({ file: relativePath, error: error.message });
|
|
252
244
|
console.log(chalk.red(` ✗ ${relativePath} - Error: ${error.message}`));
|
|
253
245
|
}
|
|
254
246
|
}
|
|
255
|
-
|
|
247
|
+
|
|
256
248
|
// Print summary
|
|
257
249
|
console.log(chalk.bold('\n─────────────────────────────────────'));
|
|
258
250
|
console.log(chalk.bold('Summary'));
|
|
@@ -265,17 +257,17 @@ async function main() {
|
|
|
265
257
|
console.log(chalk.red(` Errors: ${results.errors.length}`));
|
|
266
258
|
}
|
|
267
259
|
console.log();
|
|
268
|
-
|
|
260
|
+
|
|
269
261
|
if (dryRun && results.migrated > 0) {
|
|
270
262
|
console.log(chalk.yellow('Run without --dry-run to apply changes\n'));
|
|
271
263
|
}
|
|
272
|
-
|
|
264
|
+
|
|
273
265
|
// Exit with error code if there were errors
|
|
274
266
|
process.exit(results.errors.length > 0 ? 1 : 0);
|
|
275
267
|
}
|
|
276
268
|
|
|
277
269
|
// Run
|
|
278
|
-
main().catch(error => {
|
|
270
|
+
main().catch((error) => {
|
|
279
271
|
console.error(chalk.red('Fatal error:'), error.message);
|
|
280
272
|
process.exit(1);
|
|
281
273
|
});
|