bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.23.3b00cb36
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.yaml +68 -0
- package/.husky/pre-commit +17 -2
- package/.husky/pre-push +10 -0
- package/README.md +117 -14
- package/eslint.config.mjs +2 -2
- package/package.json +1 -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 +57 -24
- 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-cli.js +1306 -0
- package/test/test-scope-e2e.js +682 -92
- package/test/test-scope-system.js +973 -169
- package/tools/cli/bmad-cli.js +5 -0
- package/tools/cli/commands/scope.js +1250 -115
- package/tools/cli/installers/lib/modules/manager.js +6 -2
- package/tools/cli/scripts/migrate-workflows.js +43 -51
- package/.github/workflows/publish-multi-artifact.yaml +0 -50
|
@@ -9,6 +9,7 @@ const { select, text, confirm, isCancel } = require('../lib/prompts');
|
|
|
9
9
|
const { ScopeManager } = require('../../../src/core/lib/scope/scope-manager');
|
|
10
10
|
const { ScopeInitializer } = require('../../../src/core/lib/scope/scope-initializer');
|
|
11
11
|
const { ScopeValidator } = require('../../../src/core/lib/scope/scope-validator');
|
|
12
|
+
const { ScopeSync } = require('../../../src/core/lib/scope/scope-sync');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Format a date string for display
|
|
@@ -27,35 +28,35 @@ function formatDate(dateStr) {
|
|
|
27
28
|
*/
|
|
28
29
|
function displayScopeList(scopes) {
|
|
29
30
|
if (scopes.length === 0) {
|
|
30
|
-
console.log(chalk.yellow('\nNo scopes found. Create one with: bmad scope create <id>\n'));
|
|
31
|
+
console.log(chalk.yellow('\nNo scopes found. Create one with: npx bmad-fh scope create <id>\n'));
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
console.log(chalk.bold('\n Scopes:\n'));
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
// 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
|
-
|
|
38
|
+
const idWidth = Math.max(10, ...scopes.map((s) => s.id.length)) + 2;
|
|
39
|
+
const nameWidth = Math.max(15, ...scopes.map((s) => (s.name || '').length)) + 2;
|
|
40
|
+
|
|
40
41
|
// Header
|
|
41
42
|
console.log(
|
|
42
43
|
chalk.dim(' ') +
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
chalk.bold('ID'.padEnd(idWidth)) +
|
|
45
|
+
chalk.bold('Name'.padEnd(nameWidth)) +
|
|
46
|
+
chalk.bold('Status'.padEnd(10)) +
|
|
47
|
+
chalk.bold('Created'),
|
|
47
48
|
);
|
|
48
49
|
console.log(chalk.dim(' ' + '─'.repeat(idWidth + nameWidth + 10 + 20)));
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
// Rows
|
|
51
52
|
for (const scope of scopes) {
|
|
52
53
|
const statusColor = scope.status === 'active' ? chalk.green : chalk.dim;
|
|
53
54
|
console.log(
|
|
54
55
|
' ' +
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
chalk.cyan(scope.id.padEnd(idWidth)) +
|
|
57
|
+
(scope.name || scope.id).padEnd(nameWidth) +
|
|
58
|
+
statusColor(scope.status.padEnd(10)) +
|
|
59
|
+
chalk.dim(formatDate(scope.created).split(' ')[0]),
|
|
59
60
|
);
|
|
60
61
|
}
|
|
61
62
|
console.log();
|
|
@@ -69,7 +70,7 @@ function displayScopeList(scopes) {
|
|
|
69
70
|
*/
|
|
70
71
|
function displayScopeInfo(scope, paths, tree) {
|
|
71
72
|
console.log(chalk.bold(`\n Scope: ${scope.name || scope.id}\n`));
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
console.log(chalk.dim(' ─────────────────────────────────────'));
|
|
74
75
|
console.log(` ${chalk.bold('ID:')} ${chalk.cyan(scope.id)}`);
|
|
75
76
|
console.log(` ${chalk.bold('Name:')} ${scope.name || 'N/A'}`);
|
|
@@ -78,13 +79,13 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
78
79
|
console.log(` ${chalk.bold('Created:')} ${formatDate(scope.created)}`);
|
|
79
80
|
console.log(` ${chalk.bold('Last Active:')} ${formatDate(scope._meta?.last_activity)}`);
|
|
80
81
|
console.log(` ${chalk.bold('Artifacts:')} ${scope._meta?.artifact_count || 0}`);
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
console.log(chalk.dim('\n ─────────────────────────────────────'));
|
|
83
84
|
console.log(chalk.bold(' Paths:'));
|
|
84
85
|
console.log(` Planning: ${chalk.dim(paths.planning)}`);
|
|
85
86
|
console.log(` Implementation: ${chalk.dim(paths.implementation)}`);
|
|
86
87
|
console.log(` Tests: ${chalk.dim(paths.tests)}`);
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
console.log(chalk.dim('\n ─────────────────────────────────────'));
|
|
89
90
|
console.log(chalk.bold(' Dependencies:'));
|
|
90
91
|
if (tree.dependencies.length > 0) {
|
|
@@ -95,7 +96,7 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
95
96
|
} else {
|
|
96
97
|
console.log(chalk.dim(' None'));
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
console.log(chalk.bold('\n Dependents (scopes that depend on this):'));
|
|
100
101
|
if (tree.dependents.length > 0) {
|
|
101
102
|
for (const dep of tree.dependents) {
|
|
@@ -104,7 +105,7 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
104
105
|
} else {
|
|
105
106
|
console.log(chalk.dim(' None'));
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
console.log();
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -113,14 +114,24 @@ function displayScopeInfo(scope, paths, tree) {
|
|
|
113
114
|
*/
|
|
114
115
|
async function handleList(projectRoot, options) {
|
|
115
116
|
const manager = new ScopeManager({ projectRoot });
|
|
116
|
-
|
|
117
|
+
const initializer = new ScopeInitializer({ projectRoot });
|
|
118
|
+
|
|
119
|
+
// Check if system is initialized before trying to list
|
|
120
|
+
const isInitialized = await initializer.isSystemInitialized();
|
|
121
|
+
const configExists = await fs.pathExists(path.join(projectRoot, '_bmad', '_config', 'scopes.yaml'));
|
|
122
|
+
|
|
123
|
+
if (!isInitialized || !configExists) {
|
|
124
|
+
console.log(chalk.yellow('\nScope system not initialized. Run: npx bmad-fh scope init\n'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
try {
|
|
118
129
|
await manager.initialize();
|
|
119
130
|
const scopes = await manager.listScopes(options.status ? { status: options.status } : {});
|
|
120
131
|
displayScopeList(scopes);
|
|
121
132
|
} catch (error) {
|
|
122
133
|
if (error.message.includes('does not exist')) {
|
|
123
|
-
console.log(chalk.yellow('\nScope system not initialized. Run: bmad scope init\n'));
|
|
134
|
+
console.log(chalk.yellow('\nScope system not initialized. Run: npx bmad-fh scope init\n'));
|
|
124
135
|
} else {
|
|
125
136
|
throw error;
|
|
126
137
|
}
|
|
@@ -134,7 +145,7 @@ async function handleCreate(projectRoot, scopeId, options) {
|
|
|
134
145
|
const manager = new ScopeManager({ projectRoot });
|
|
135
146
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
136
147
|
const validator = new ScopeValidator();
|
|
137
|
-
|
|
148
|
+
|
|
138
149
|
// If no scopeId provided, prompt for it
|
|
139
150
|
if (!scopeId) {
|
|
140
151
|
const inputId = await text({
|
|
@@ -143,87 +154,85 @@ async function handleCreate(projectRoot, scopeId, options) {
|
|
|
143
154
|
validate: (value) => {
|
|
144
155
|
const result = validator.validateScopeId(value);
|
|
145
156
|
return result.valid ? undefined : result.error;
|
|
146
|
-
}
|
|
157
|
+
},
|
|
147
158
|
});
|
|
148
|
-
|
|
159
|
+
|
|
149
160
|
if (isCancel(inputId)) {
|
|
150
161
|
console.log(chalk.yellow('Cancelled.'));
|
|
151
162
|
return;
|
|
152
163
|
}
|
|
153
164
|
scopeId = inputId;
|
|
154
165
|
}
|
|
155
|
-
|
|
166
|
+
|
|
156
167
|
// Validate scope ID
|
|
157
168
|
const idValidation = validator.validateScopeId(scopeId);
|
|
158
169
|
if (!idValidation.valid) {
|
|
159
170
|
console.error(chalk.red(`Error: ${idValidation.error}`));
|
|
160
171
|
process.exit(1);
|
|
161
172
|
}
|
|
162
|
-
|
|
173
|
+
|
|
163
174
|
// Get scope name if not provided
|
|
164
175
|
let name = options.name;
|
|
165
176
|
if (!name) {
|
|
166
177
|
const inputName = await text({
|
|
167
178
|
message: 'Enter scope name (human-readable):',
|
|
168
179
|
placeholder: `e.g., Authentication Service`,
|
|
169
|
-
initialValue: scopeId.charAt(0).toUpperCase() + scopeId.slice(1).replaceAll('-', ' ')
|
|
180
|
+
initialValue: scopeId.charAt(0).toUpperCase() + scopeId.slice(1).replaceAll('-', ' '),
|
|
170
181
|
});
|
|
171
|
-
|
|
182
|
+
|
|
172
183
|
if (isCancel(inputName)) {
|
|
173
184
|
console.log(chalk.yellow('Cancelled.'));
|
|
174
185
|
return;
|
|
175
186
|
}
|
|
176
187
|
name = inputName;
|
|
177
188
|
}
|
|
178
|
-
|
|
179
|
-
// Get description if not provided
|
|
189
|
+
|
|
190
|
+
// Get description if not provided (check for undefined specifically since empty string is valid)
|
|
180
191
|
let description = options.description;
|
|
181
|
-
if (
|
|
192
|
+
if (description === undefined) {
|
|
182
193
|
const inputDesc = await text({
|
|
183
194
|
message: 'Enter scope description (optional):',
|
|
184
|
-
placeholder: 'Brief description of this scope'
|
|
195
|
+
placeholder: 'Brief description of this scope',
|
|
185
196
|
});
|
|
186
|
-
|
|
197
|
+
|
|
187
198
|
if (isCancel(inputDesc)) {
|
|
188
199
|
console.log(chalk.yellow('Cancelled.'));
|
|
189
200
|
return;
|
|
190
201
|
}
|
|
191
202
|
description = inputDesc || '';
|
|
192
203
|
}
|
|
193
|
-
|
|
204
|
+
|
|
194
205
|
console.log(chalk.blue('\nCreating scope...'));
|
|
195
|
-
|
|
206
|
+
|
|
196
207
|
// Initialize scope system if needed
|
|
197
208
|
await manager.initialize();
|
|
198
|
-
|
|
209
|
+
|
|
199
210
|
// Check if system is initialized
|
|
200
211
|
const systemInit = await initializer.isSystemInitialized();
|
|
201
212
|
if (!systemInit) {
|
|
202
213
|
console.log(chalk.blue('Initializing scope system...'));
|
|
203
214
|
await initializer.initializeScopeSystem();
|
|
204
215
|
}
|
|
205
|
-
|
|
206
|
-
// Create scope in configuration
|
|
216
|
+
|
|
217
|
+
// Create scope in configuration and directory structure
|
|
218
|
+
// Note: manager.createScope() now also calls initializer.initializeScope() internally
|
|
207
219
|
const scope = await manager.createScope(scopeId, {
|
|
208
220
|
name,
|
|
209
221
|
description,
|
|
210
|
-
dependencies: options.dependencies ? options.dependencies.split(',').map(d => d.trim()) : []
|
|
222
|
+
dependencies: options.dependencies ? options.dependencies.split(',').map((d) => d.trim()) : [],
|
|
223
|
+
createContext: options.context,
|
|
211
224
|
});
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
const paths =
|
|
215
|
-
|
|
216
|
-
description,
|
|
217
|
-
createContext: options.context
|
|
218
|
-
});
|
|
219
|
-
|
|
225
|
+
|
|
226
|
+
// Get paths for display
|
|
227
|
+
const paths = initializer.getScopePaths(scopeId);
|
|
228
|
+
|
|
220
229
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' created successfully!\n`));
|
|
221
230
|
console.log(chalk.dim(' Directories created:'));
|
|
222
231
|
console.log(` ${paths.planning}`);
|
|
223
232
|
console.log(` ${paths.implementation}`);
|
|
224
233
|
console.log(` ${paths.tests}`);
|
|
225
234
|
console.log();
|
|
226
|
-
console.log(chalk.cyan(` Use with
|
|
235
|
+
console.log(chalk.cyan(` Use with workflows by setting .bmad-scope or using BMAD_SCOPE=${scopeId}`));
|
|
227
236
|
console.log();
|
|
228
237
|
}
|
|
229
238
|
|
|
@@ -232,23 +241,23 @@ async function handleCreate(projectRoot, scopeId, options) {
|
|
|
232
241
|
*/
|
|
233
242
|
async function handleInfo(projectRoot, scopeId) {
|
|
234
243
|
if (!scopeId) {
|
|
235
|
-
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope info <scope-id>'));
|
|
244
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope info <scope-id>'));
|
|
236
245
|
process.exit(1);
|
|
237
246
|
}
|
|
238
|
-
|
|
247
|
+
|
|
239
248
|
const manager = new ScopeManager({ projectRoot });
|
|
240
|
-
|
|
249
|
+
|
|
241
250
|
await manager.initialize();
|
|
242
251
|
const scope = await manager.getScope(scopeId);
|
|
243
|
-
|
|
252
|
+
|
|
244
253
|
if (!scope) {
|
|
245
254
|
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
246
255
|
process.exit(1);
|
|
247
256
|
}
|
|
248
|
-
|
|
257
|
+
|
|
249
258
|
const paths = await manager.getScopePaths(scopeId);
|
|
250
259
|
const tree = await manager.getDependencyTree(scopeId);
|
|
251
|
-
|
|
260
|
+
|
|
252
261
|
displayScopeInfo(scope, paths, tree);
|
|
253
262
|
}
|
|
254
263
|
|
|
@@ -257,44 +266,46 @@ async function handleInfo(projectRoot, scopeId) {
|
|
|
257
266
|
*/
|
|
258
267
|
async function handleRemove(projectRoot, scopeId, options) {
|
|
259
268
|
if (!scopeId) {
|
|
260
|
-
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope remove <scope-id>'));
|
|
269
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope remove <scope-id>'));
|
|
261
270
|
process.exit(1);
|
|
262
271
|
}
|
|
263
|
-
|
|
272
|
+
|
|
264
273
|
const manager = new ScopeManager({ projectRoot });
|
|
265
274
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
266
|
-
|
|
275
|
+
|
|
267
276
|
await manager.initialize();
|
|
268
|
-
|
|
277
|
+
|
|
269
278
|
const scope = await manager.getScope(scopeId);
|
|
270
279
|
if (!scope) {
|
|
271
280
|
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
272
281
|
process.exit(1);
|
|
273
282
|
}
|
|
274
|
-
|
|
283
|
+
|
|
275
284
|
// Confirm removal unless --force
|
|
276
285
|
if (!options.force) {
|
|
277
286
|
const confirmed = await confirm({
|
|
278
287
|
message: `Are you sure you want to remove scope '${scopeId}'? This will delete all scope artifacts!`,
|
|
279
|
-
initialValue: false
|
|
288
|
+
initialValue: false,
|
|
280
289
|
});
|
|
281
|
-
|
|
290
|
+
|
|
282
291
|
if (isCancel(confirmed) || !confirmed) {
|
|
283
292
|
console.log(chalk.yellow('Cancelled.'));
|
|
284
293
|
return;
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
|
-
|
|
296
|
+
|
|
288
297
|
console.log(chalk.blue('\nRemoving scope...'));
|
|
289
|
-
|
|
298
|
+
|
|
290
299
|
// Remove scope directory (with backup)
|
|
291
|
-
|
|
292
|
-
|
|
300
|
+
// Note: Commander.js sets options.backup to false when --no-backup is passed
|
|
301
|
+
const shouldBackup = options.backup !== false;
|
|
302
|
+
await initializer.removeScope(scopeId, { backup: shouldBackup });
|
|
303
|
+
|
|
293
304
|
// Remove from configuration
|
|
294
305
|
await manager.removeScope(scopeId, { force: true });
|
|
295
|
-
|
|
306
|
+
|
|
296
307
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' removed successfully!`));
|
|
297
|
-
if (
|
|
308
|
+
if (shouldBackup) {
|
|
298
309
|
console.log(chalk.dim(' A backup was created in _bmad-output/'));
|
|
299
310
|
}
|
|
300
311
|
console.log();
|
|
@@ -306,19 +317,19 @@ async function handleRemove(projectRoot, scopeId, options) {
|
|
|
306
317
|
async function handleInit(projectRoot) {
|
|
307
318
|
const manager = new ScopeManager({ projectRoot });
|
|
308
319
|
const initializer = new ScopeInitializer({ projectRoot });
|
|
309
|
-
|
|
320
|
+
|
|
310
321
|
console.log(chalk.blue('\nInitializing scope system...'));
|
|
311
|
-
|
|
322
|
+
|
|
312
323
|
await manager.initialize();
|
|
313
324
|
await initializer.initializeScopeSystem();
|
|
314
|
-
|
|
325
|
+
|
|
315
326
|
console.log(chalk.green('\n✓ Scope system initialized successfully!\n'));
|
|
316
327
|
console.log(chalk.dim(' Created:'));
|
|
317
328
|
console.log(` ${chalk.cyan('_bmad/_config/scopes.yaml')} - Scope configuration`);
|
|
318
329
|
console.log(` ${chalk.cyan('_bmad-output/_shared/')} - Shared knowledge layer`);
|
|
319
330
|
console.log(` ${chalk.cyan('_bmad/_events/')} - Event system`);
|
|
320
331
|
console.log();
|
|
321
|
-
console.log(chalk.cyan(' Next: bmad scope create <scope-id>'));
|
|
332
|
+
console.log(chalk.cyan(' Next: npx bmad-fh scope create <scope-id>'));
|
|
322
333
|
console.log();
|
|
323
334
|
}
|
|
324
335
|
|
|
@@ -327,15 +338,15 @@ async function handleInit(projectRoot) {
|
|
|
327
338
|
*/
|
|
328
339
|
async function handleArchive(projectRoot, scopeId) {
|
|
329
340
|
if (!scopeId) {
|
|
330
|
-
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope archive <scope-id>'));
|
|
341
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope archive <scope-id>'));
|
|
331
342
|
process.exit(1);
|
|
332
343
|
}
|
|
333
|
-
|
|
344
|
+
|
|
334
345
|
const manager = new ScopeManager({ projectRoot });
|
|
335
|
-
|
|
346
|
+
|
|
336
347
|
await manager.initialize();
|
|
337
348
|
await manager.archiveScope(scopeId);
|
|
338
|
-
|
|
349
|
+
|
|
339
350
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' archived.\n`));
|
|
340
351
|
}
|
|
341
352
|
|
|
@@ -344,52 +355,1137 @@ async function handleArchive(projectRoot, scopeId) {
|
|
|
344
355
|
*/
|
|
345
356
|
async function handleActivate(projectRoot, scopeId) {
|
|
346
357
|
if (!scopeId) {
|
|
347
|
-
console.error(chalk.red('Error: Scope ID is required. Usage: bmad scope activate <scope-id>'));
|
|
358
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope activate <scope-id>'));
|
|
348
359
|
process.exit(1);
|
|
349
360
|
}
|
|
350
|
-
|
|
361
|
+
|
|
351
362
|
const manager = new ScopeManager({ projectRoot });
|
|
352
|
-
|
|
363
|
+
|
|
353
364
|
await manager.initialize();
|
|
354
365
|
await manager.activateScope(scopeId);
|
|
355
|
-
|
|
366
|
+
|
|
356
367
|
console.log(chalk.green(`\n✓ Scope '${scopeId}' activated.\n`));
|
|
357
368
|
}
|
|
358
369
|
|
|
359
370
|
/**
|
|
360
|
-
*
|
|
371
|
+
* Handle 'sync-up' subcommand - Promote scope artifacts to shared layer
|
|
372
|
+
*/
|
|
373
|
+
async function handleSyncUp(projectRoot, scopeId, options) {
|
|
374
|
+
if (!scopeId) {
|
|
375
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope sync-up <scope-id>'));
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const manager = new ScopeManager({ projectRoot });
|
|
380
|
+
const sync = new ScopeSync({ projectRoot });
|
|
381
|
+
|
|
382
|
+
await manager.initialize();
|
|
383
|
+
|
|
384
|
+
// Verify scope exists
|
|
385
|
+
const scope = await manager.getScope(scopeId);
|
|
386
|
+
if (!scope) {
|
|
387
|
+
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Handle dry-run mode
|
|
392
|
+
if (options.dryRun) {
|
|
393
|
+
console.log(chalk.blue(`\n[Dry Run] Analyzing artifacts in '${scopeId}' for promotion...`));
|
|
394
|
+
|
|
395
|
+
// Get sync status to show what would be promoted
|
|
396
|
+
const scopePath = path.join(projectRoot, '_bmad-output', scopeId);
|
|
397
|
+
const promotablePatterns = ['architecture/*.md', 'contracts/*.md', 'principles/*.md', 'project-context.md'];
|
|
398
|
+
|
|
399
|
+
console.log(chalk.yellow('\n Would promote files matching these patterns:\n'));
|
|
400
|
+
for (const pattern of promotablePatterns) {
|
|
401
|
+
console.log(` ${chalk.cyan('•')} ${pattern}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const status = await sync.getSyncStatus(scopeId);
|
|
406
|
+
if (status.promotedCount > 0) {
|
|
407
|
+
console.log(chalk.dim(`\n Previously promoted: ${status.promotedCount} files`));
|
|
408
|
+
for (const file of status.promotedFiles) {
|
|
409
|
+
console.log(` ${chalk.dim('✓')} ${file}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
// Ignore errors getting status
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
console.log(chalk.dim('\n Run without --dry-run to execute.\n'));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
console.log(chalk.blue(`\nPromoting artifacts from '${scopeId}' to shared layer...`));
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// syncUp signature: syncUp(scopeId, files = null, options = {})
|
|
424
|
+
const syncOptions = {
|
|
425
|
+
force: options.resolution === 'keep-local' ? false : true,
|
|
426
|
+
};
|
|
427
|
+
const result = await sync.syncUp(scopeId, null, syncOptions);
|
|
428
|
+
|
|
429
|
+
if (result.success) {
|
|
430
|
+
console.log(chalk.green('\n✓ Sync-up complete!\n'));
|
|
431
|
+
} else {
|
|
432
|
+
console.log(chalk.yellow('\n⚠ Sync-up completed with issues.\n'));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Handle promoted files - result.promoted is array of { file, target }
|
|
436
|
+
if (result.promoted && result.promoted.length > 0) {
|
|
437
|
+
console.log(chalk.dim(' Promoted files:'));
|
|
438
|
+
for (const item of result.promoted) {
|
|
439
|
+
const displayFile = typeof item === 'string' ? item : item.file;
|
|
440
|
+
console.log(` ${chalk.cyan('→')} ${displayFile}`);
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
console.log(chalk.dim(' No files to promote (already in sync or no promotable artifacts).'));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Handle skipped files - result.skipped is array of { file, reason }
|
|
447
|
+
if (result.skipped && result.skipped.length > 0) {
|
|
448
|
+
console.log(chalk.dim('\n Skipped files:'));
|
|
449
|
+
for (const item of result.skipped) {
|
|
450
|
+
const file = typeof item === 'string' ? item : item.file;
|
|
451
|
+
const reason = typeof item === 'object' ? item.reason : 'unknown';
|
|
452
|
+
console.log(` ${chalk.yellow('○')} ${file} - ${reason}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Handle conflicts - result.conflicts is array of { file, source, target, resolution }
|
|
457
|
+
if (result.conflicts && result.conflicts.length > 0) {
|
|
458
|
+
console.log(chalk.yellow('\n Conflicts detected:'));
|
|
459
|
+
for (const conflict of result.conflicts) {
|
|
460
|
+
const file = typeof conflict === 'string' ? conflict : conflict.file;
|
|
461
|
+
console.log(` ${chalk.yellow('!')} ${file}`);
|
|
462
|
+
|
|
463
|
+
// Attempt to resolve conflict if resolution strategy provided
|
|
464
|
+
if (options.resolution && options.resolution !== 'prompt') {
|
|
465
|
+
const resolveResult = await sync.resolveConflict(conflict, options.resolution);
|
|
466
|
+
if (resolveResult.success) {
|
|
467
|
+
console.log(` ${chalk.green('✓')} Resolved: ${resolveResult.action}`);
|
|
468
|
+
} else {
|
|
469
|
+
console.log(` ${chalk.red('✗')} Failed: ${resolveResult.error}`);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
console.log(` ${chalk.dim('Use --resolution to auto-resolve')}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Handle errors - result.errors is array of { file, error } or { error }
|
|
478
|
+
if (result.errors && result.errors.length > 0) {
|
|
479
|
+
console.log(chalk.red('\n Errors:'));
|
|
480
|
+
for (const err of result.errors) {
|
|
481
|
+
if (err.file) {
|
|
482
|
+
console.log(` ${chalk.red('✗')} ${err.file}: ${err.error}`);
|
|
483
|
+
} else {
|
|
484
|
+
console.log(` ${chalk.red('✗')} ${err.error}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
console.log();
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error(chalk.red(`\nSync-up failed: ${error.message}`));
|
|
492
|
+
if (process.env.DEBUG) {
|
|
493
|
+
console.error(chalk.dim(error.stack));
|
|
494
|
+
}
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Handle 'sync-down' subcommand - Pull shared layer updates into scope
|
|
501
|
+
*/
|
|
502
|
+
async function handleSyncDown(projectRoot, scopeId, options) {
|
|
503
|
+
if (!scopeId) {
|
|
504
|
+
console.error(chalk.red('Error: Scope ID is required. Usage: npx bmad-fh scope sync-down <scope-id>'));
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const manager = new ScopeManager({ projectRoot });
|
|
509
|
+
const sync = new ScopeSync({ projectRoot });
|
|
510
|
+
|
|
511
|
+
await manager.initialize();
|
|
512
|
+
|
|
513
|
+
// Verify scope exists
|
|
514
|
+
const scope = await manager.getScope(scopeId);
|
|
515
|
+
if (!scope) {
|
|
516
|
+
console.error(chalk.red(`Error: Scope '${scopeId}' not found.`));
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Handle dry-run mode
|
|
521
|
+
if (options.dryRun) {
|
|
522
|
+
console.log(chalk.blue(`\n[Dry Run] Analyzing shared layer for updates to '${scopeId}'...`));
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
const status = await sync.getSyncStatus(scopeId);
|
|
526
|
+
console.log(chalk.dim(`\n Last sync-down: ${status.lastSyncDown || 'Never'}`));
|
|
527
|
+
if (status.pulledCount > 0) {
|
|
528
|
+
console.log(chalk.dim(` Previously pulled: ${status.pulledCount} files`));
|
|
529
|
+
for (const file of status.pulledFiles) {
|
|
530
|
+
console.log(` ${chalk.dim('✓')} ${file}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch {
|
|
534
|
+
// Ignore errors getting status
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
console.log(chalk.dim('\n Run without --dry-run to execute.\n'));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
console.log(chalk.blue(`\nPulling shared layer updates into '${scopeId}'...`));
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
// syncDown signature: syncDown(scopeId, options = {})
|
|
545
|
+
const syncOptions = {
|
|
546
|
+
force: options.resolution === 'keep-shared',
|
|
547
|
+
resolution: options.resolution || 'keep-local',
|
|
548
|
+
};
|
|
549
|
+
const result = await sync.syncDown(scopeId, syncOptions);
|
|
550
|
+
|
|
551
|
+
if (result.success) {
|
|
552
|
+
console.log(chalk.green('\n✓ Sync-down complete!\n'));
|
|
553
|
+
} else {
|
|
554
|
+
console.log(chalk.yellow('\n⚠ Sync-down completed with issues.\n'));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Handle pulled files - result.pulled is array of { file, scope, target }
|
|
558
|
+
if (result.pulled && result.pulled.length > 0) {
|
|
559
|
+
console.log(chalk.dim(' Pulled files:'));
|
|
560
|
+
for (const item of result.pulled) {
|
|
561
|
+
const displayFile = typeof item === 'string' ? item : `${item.scope}/${item.file}`;
|
|
562
|
+
console.log(` ${chalk.cyan('←')} ${displayFile}`);
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
console.log(chalk.dim(' No new files to pull.'));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Handle up-to-date files - result.upToDate is array of { file, scope }
|
|
569
|
+
if (result.upToDate && result.upToDate.length > 0) {
|
|
570
|
+
console.log(chalk.dim('\n Already up-to-date:'));
|
|
571
|
+
for (const item of result.upToDate) {
|
|
572
|
+
const displayFile = typeof item === 'string' ? item : `${item.scope}/${item.file}`;
|
|
573
|
+
console.log(` ${chalk.green('✓')} ${displayFile}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Handle conflicts - result.conflicts is array of { file, scope, local, shared, resolution }
|
|
578
|
+
if (result.conflicts && result.conflicts.length > 0) {
|
|
579
|
+
console.log(chalk.yellow('\n Conflicts detected:'));
|
|
580
|
+
for (const conflict of result.conflicts) {
|
|
581
|
+
const file = typeof conflict === 'string' ? conflict : `${conflict.scope}/${conflict.file}`;
|
|
582
|
+
console.log(` ${chalk.yellow('!')} ${file}`);
|
|
583
|
+
|
|
584
|
+
// Attempt to resolve conflict if resolution strategy provided
|
|
585
|
+
if (options.resolution && options.resolution !== 'prompt') {
|
|
586
|
+
const resolveResult = await sync.resolveConflict(conflict, options.resolution);
|
|
587
|
+
if (resolveResult.success) {
|
|
588
|
+
console.log(` ${chalk.green('✓')} Resolved: ${resolveResult.action}`);
|
|
589
|
+
} else {
|
|
590
|
+
console.log(` ${chalk.red('✗')} Failed: ${resolveResult.error}`);
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
console.log(` ${chalk.dim('Use --resolution to auto-resolve')}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Handle errors - result.errors is array of { file, error } or { error }
|
|
599
|
+
if (result.errors && result.errors.length > 0) {
|
|
600
|
+
console.log(chalk.red('\n Errors:'));
|
|
601
|
+
for (const err of result.errors) {
|
|
602
|
+
if (err.file) {
|
|
603
|
+
console.log(` ${chalk.red('✗')} ${err.file}: ${err.error}`);
|
|
604
|
+
} else {
|
|
605
|
+
console.log(` ${chalk.red('✗')} ${err.error}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
console.log();
|
|
611
|
+
} catch (error) {
|
|
612
|
+
console.error(chalk.red(`\nSync-down failed: ${error.message}`));
|
|
613
|
+
if (process.env.DEBUG) {
|
|
614
|
+
console.error(chalk.dim(error.stack));
|
|
615
|
+
}
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Handle 'set' subcommand - Set the active scope for the session
|
|
622
|
+
*/
|
|
623
|
+
async function handleSet(projectRoot, scopeId, options) {
|
|
624
|
+
const manager = new ScopeManager({ projectRoot });
|
|
625
|
+
const scopeFilePath = path.join(projectRoot, '.bmad-scope');
|
|
626
|
+
|
|
627
|
+
// If no scopeId provided, show current scope or prompt
|
|
628
|
+
if (!scopeId) {
|
|
629
|
+
// Check if there's a current scope
|
|
630
|
+
try {
|
|
631
|
+
if (await fs.pathExists(scopeFilePath)) {
|
|
632
|
+
const content = await fs.readFile(scopeFilePath, 'utf8');
|
|
633
|
+
const match = content.match(/active_scope:\s*(\S+)/);
|
|
634
|
+
if (match) {
|
|
635
|
+
console.log(chalk.blue(`\nCurrent active scope: ${chalk.cyan(match[1])}\n`));
|
|
636
|
+
|
|
637
|
+
// Offer to change
|
|
638
|
+
const scopes = await manager.listScopes({ status: 'active' });
|
|
639
|
+
if (scopes.length === 0) {
|
|
640
|
+
console.log(chalk.yellow('No active scopes available. Create one with: npx bmad-fh scope create <id>\n'));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const choices = scopes.map((s) => ({ value: s.id, label: `${s.id} - ${s.name || 'No name'}` }));
|
|
645
|
+
choices.push({ value: '__clear__', label: 'Clear active scope' });
|
|
646
|
+
|
|
647
|
+
const selected = await select({
|
|
648
|
+
message: 'Select scope to activate:',
|
|
649
|
+
options: choices,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
if (isCancel(selected)) {
|
|
653
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (selected === '__clear__') {
|
|
658
|
+
await fs.remove(scopeFilePath);
|
|
659
|
+
console.log(chalk.green('\n✓ Active scope cleared.\n'));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
scopeId = selected;
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
// No current scope, prompt to select
|
|
667
|
+
await manager.initialize();
|
|
668
|
+
const scopes = await manager.listScopes({ status: 'active' });
|
|
669
|
+
|
|
670
|
+
if (scopes.length === 0) {
|
|
671
|
+
console.log(chalk.yellow('\nNo scopes available. Create one first:\n'));
|
|
672
|
+
console.log(` ${chalk.cyan('npx bmad-fh scope create <id>')}\n`);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const choices = scopes.map((s) => ({ value: s.id, label: `${s.id} - ${s.name || 'No name'}` }));
|
|
677
|
+
|
|
678
|
+
const selected = await select({
|
|
679
|
+
message: 'Select scope to activate:',
|
|
680
|
+
options: choices,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (isCancel(selected)) {
|
|
684
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
scopeId = selected;
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (error.message.includes('does not exist')) {
|
|
692
|
+
console.log(chalk.yellow('\nScope system not initialized. Run: npx bmad-fh scope init\n'));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Validate scope exists
|
|
700
|
+
try {
|
|
701
|
+
await manager.initialize();
|
|
702
|
+
const scope = await manager.getScope(scopeId);
|
|
703
|
+
|
|
704
|
+
if (!scope) {
|
|
705
|
+
console.error(chalk.red(`\nError: Scope '${scopeId}' not found.`));
|
|
706
|
+
console.log(chalk.dim('Available scopes:'));
|
|
707
|
+
const scopes = await manager.listScopes({ status: 'active' });
|
|
708
|
+
for (const s of scopes) {
|
|
709
|
+
console.log(` ${chalk.cyan(s.id)} - ${s.name || 'No name'}`);
|
|
710
|
+
}
|
|
711
|
+
console.log();
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (scope.status === 'archived') {
|
|
716
|
+
console.error(chalk.yellow(`\nWarning: Scope '${scopeId}' is archived. Activate it first with:`));
|
|
717
|
+
console.log(` ${chalk.cyan(`npx bmad-fh scope activate ${scopeId}`)}\n`);
|
|
718
|
+
|
|
719
|
+
const proceed = await confirm({
|
|
720
|
+
message: 'Set as active scope anyway?',
|
|
721
|
+
initialValue: false,
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
if (isCancel(proceed) || !proceed) {
|
|
725
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} catch (error) {
|
|
730
|
+
if (error.message.includes('does not exist')) {
|
|
731
|
+
console.log(chalk.yellow('\nScope system not initialized. Run: npx bmad-fh scope init\n'));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Write .bmad-scope file
|
|
738
|
+
const scopeContent = `# BMAD Active Scope Configuration
|
|
739
|
+
# This file is auto-generated. Do not edit manually.
|
|
740
|
+
# To change: npx bmad-fh scope set <scope-id>
|
|
741
|
+
|
|
742
|
+
active_scope: ${scopeId}
|
|
743
|
+
set_at: "${new Date().toISOString()}"
|
|
744
|
+
`;
|
|
745
|
+
|
|
746
|
+
await fs.writeFile(scopeFilePath, scopeContent, 'utf8');
|
|
747
|
+
|
|
748
|
+
console.log(chalk.green(`\n✓ Active scope set to '${scopeId}'`));
|
|
749
|
+
console.log(chalk.dim(` File: ${scopeFilePath}`));
|
|
750
|
+
console.log(chalk.dim('\n Workflows will now use this scope automatically.'));
|
|
751
|
+
console.log(chalk.dim(' You can also use BMAD_SCOPE environment variable to override.\n'));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Handle 'unset' subcommand - Clear the active scope
|
|
756
|
+
*/
|
|
757
|
+
async function handleUnset(projectRoot) {
|
|
758
|
+
const scopeFilePath = path.join(projectRoot, '.bmad-scope');
|
|
759
|
+
|
|
760
|
+
if (await fs.pathExists(scopeFilePath)) {
|
|
761
|
+
await fs.remove(scopeFilePath);
|
|
762
|
+
console.log(chalk.green('\n✓ Active scope cleared.\n'));
|
|
763
|
+
console.log(chalk.dim(' Workflows will now prompt for scope selection.\n'));
|
|
764
|
+
} else {
|
|
765
|
+
console.log(chalk.yellow('\n No active scope is set.\n'));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Show comprehensive help for scope command
|
|
361
771
|
*/
|
|
362
772
|
function showHelp() {
|
|
363
|
-
console.log(chalk.bold('\n BMAD Scope Management
|
|
364
|
-
console.log('
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
console.log(
|
|
368
|
-
console.log(
|
|
369
|
-
console.log(
|
|
370
|
-
console.log(
|
|
371
|
-
|
|
372
|
-
console.log(
|
|
773
|
+
console.log(chalk.bold('\n BMAD Scope Management'));
|
|
774
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
775
|
+
|
|
776
|
+
// Overview
|
|
777
|
+
console.log(chalk.bold(' OVERVIEW\n'));
|
|
778
|
+
console.log(' The scope system enables parallel development by isolating artifacts into');
|
|
779
|
+
console.log(' separate workspaces. Each scope maintains its own planning artifacts,');
|
|
780
|
+
console.log(' implementation artifacts, tests, and optionally a scope-specific context.\n');
|
|
781
|
+
|
|
782
|
+
console.log(chalk.dim(' Key Benefits:'));
|
|
783
|
+
console.log(' • Run multiple workflows in parallel across different terminal sessions');
|
|
784
|
+
console.log(' • Isolated artifacts prevent cross-contamination between features/services');
|
|
785
|
+
console.log(' • Shared knowledge layer for cross-cutting concerns and contracts');
|
|
786
|
+
console.log(' • Event system notifies dependent scopes of changes');
|
|
787
|
+
console.log(' • Session-sticky scope context for seamless workflow integration\n');
|
|
788
|
+
|
|
789
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
790
|
+
|
|
791
|
+
// Commands
|
|
792
|
+
console.log(chalk.bold(' COMMANDS\n'));
|
|
793
|
+
console.log(` ${chalk.cyan('init')} Initialize scope system in current project`);
|
|
794
|
+
console.log(` ${chalk.cyan('list')} ${chalk.dim('[options]')} List all scopes (aliases: ls)`);
|
|
795
|
+
console.log(` ${chalk.cyan('create')} ${chalk.dim('[id] [opts]')} Create a new scope (aliases: new)`);
|
|
796
|
+
console.log(` ${chalk.cyan('info')} ${chalk.dim('<id>')} Show detailed scope information (aliases: show)`);
|
|
797
|
+
console.log(` ${chalk.cyan('remove')} ${chalk.dim('<id> [opts]')} Remove a scope and its artifacts (aliases: rm, delete)`);
|
|
798
|
+
console.log(` ${chalk.cyan('archive')} ${chalk.dim('<id>')} Archive a scope (preserves artifacts, excludes from list)`);
|
|
799
|
+
console.log(` ${chalk.cyan('activate')} ${chalk.dim('<id>')} Reactivate an archived scope`);
|
|
800
|
+
console.log(` ${chalk.cyan('set')} ${chalk.dim('[id]')} Set active scope for session (alias: use)`);
|
|
801
|
+
console.log(` ${chalk.cyan('unset')} Clear active scope (alias: clear)`);
|
|
802
|
+
console.log(` ${chalk.cyan('sync-up')} ${chalk.dim('<id> [opts]')} Promote scope artifacts to shared layer (alias: syncup)`);
|
|
803
|
+
console.log(` ${chalk.cyan('sync-down')} ${chalk.dim('<id> [opts]')} Pull shared layer updates into scope (alias: syncdown)`);
|
|
804
|
+
console.log(` ${chalk.cyan('help')} ${chalk.dim('[command]')} Show help (add command name for detailed help)`);
|
|
373
805
|
console.log();
|
|
374
|
-
|
|
375
|
-
console.log(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
console.log(
|
|
379
|
-
console.log(
|
|
806
|
+
|
|
807
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
808
|
+
|
|
809
|
+
// Options
|
|
810
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
811
|
+
console.log(chalk.dim(' Create options:'));
|
|
812
|
+
console.log(` ${chalk.cyan('-n, --name')} ${chalk.dim('<name>')} Human-readable scope name`);
|
|
813
|
+
console.log(` ${chalk.cyan('-d, --description')} ${chalk.dim('<text>')} Brief description of scope purpose`);
|
|
814
|
+
console.log(` ${chalk.cyan('--deps')} ${chalk.dim('<ids>')} Comma-separated dependency scope IDs`);
|
|
815
|
+
console.log(` ${chalk.cyan('--context')} Create scope-specific project-context.md\n`);
|
|
816
|
+
|
|
817
|
+
console.log(chalk.dim(' Remove options:'));
|
|
818
|
+
console.log(` ${chalk.cyan('-f, --force')} Skip confirmation prompt`);
|
|
819
|
+
console.log(` ${chalk.cyan('--no-backup')} Don't create backup before removal\n`);
|
|
820
|
+
|
|
821
|
+
console.log(chalk.dim(' List options:'));
|
|
822
|
+
console.log(` ${chalk.cyan('-s, --status')} ${chalk.dim('<status>')} Filter by status (active|archived)\n`);
|
|
823
|
+
|
|
824
|
+
console.log(chalk.dim(' Sync options:'));
|
|
825
|
+
console.log(` ${chalk.cyan('--dry-run')} Show what would be synced without changes`);
|
|
826
|
+
console.log(
|
|
827
|
+
` ${chalk.cyan('--resolution')} ${chalk.dim('<strategy>')} Conflict resolution: keep-local|keep-shared|backup-and-update\n`,
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
831
|
+
|
|
832
|
+
// Quick Start
|
|
833
|
+
console.log(chalk.bold(' QUICK START\n'));
|
|
834
|
+
console.log(chalk.dim(' 1. Initialize the scope system:'));
|
|
835
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope init\n`);
|
|
836
|
+
console.log(chalk.dim(' 2. Create your first scope:'));
|
|
837
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create auth --name "Authentication Service"\n`);
|
|
838
|
+
console.log(chalk.dim(' 3. Set the active scope for your session:'));
|
|
839
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope set auth\n`);
|
|
840
|
+
console.log(chalk.dim(' 4. Run workflows - artifacts go to scope directory:'));
|
|
841
|
+
console.log(` Workflows automatically detect scope from .bmad-scope`);
|
|
842
|
+
console.log(` Your PRD, architecture, etc. are isolated in _bmad-output/auth/\n`);
|
|
843
|
+
console.log(chalk.dim(' Alternative: Use BMAD_SCOPE environment variable to override:\n'));
|
|
844
|
+
console.log(` ${chalk.green('$')} BMAD_SCOPE=auth npx bmad-fh ...\n`);
|
|
845
|
+
|
|
846
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
847
|
+
|
|
848
|
+
// Examples
|
|
849
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
850
|
+
console.log(chalk.dim(' Basic workflow:'));
|
|
851
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope init`);
|
|
852
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create auth --name "Auth" --description "User authentication"`);
|
|
853
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create payments --name "Payments" --deps auth`);
|
|
854
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope list\n`);
|
|
855
|
+
|
|
856
|
+
console.log(chalk.dim(' Parallel development (two terminals):'));
|
|
857
|
+
console.log(` ${chalk.green('# Terminal 1:')} ${chalk.green('# Terminal 2:')}`);
|
|
858
|
+
console.log(` ${chalk.dim('$')} npx bmad-fh scope create auth ${chalk.dim('$')} npx bmad-fh scope create payments`);
|
|
859
|
+
console.log(` ${chalk.dim('# Run PRD workflow for auth')} ${chalk.dim('# Run PRD workflow for payments')}\n`);
|
|
860
|
+
|
|
861
|
+
console.log(chalk.dim(' Sharing artifacts between scopes:'));
|
|
862
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-up auth ${chalk.dim('# Promote auth artifacts to _shared/')}`);
|
|
863
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-down payments ${chalk.dim('# Pull shared updates into payments')}\n`);
|
|
864
|
+
|
|
865
|
+
console.log(chalk.dim(' Lifecycle management:'));
|
|
866
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope archive auth ${chalk.dim('# Archive when feature is complete')}`);
|
|
867
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope activate auth ${chalk.dim('# Reactivate if needed later')}`);
|
|
868
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope remove auth ${chalk.dim('# Remove with backup')}`);
|
|
869
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope remove auth --force --no-backup ${chalk.dim('# Force remove')}\n`);
|
|
870
|
+
|
|
871
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
872
|
+
|
|
873
|
+
// Directory Structure
|
|
874
|
+
console.log(chalk.bold(' DIRECTORY STRUCTURE\n'));
|
|
875
|
+
console.log(chalk.dim(' After initialization and scope creation:'));
|
|
380
876
|
console.log();
|
|
381
|
-
console.log(
|
|
382
|
-
console.log(
|
|
383
|
-
console.log(
|
|
384
|
-
console.log(` ${chalk.
|
|
385
|
-
console.log(
|
|
386
|
-
console.log(` ${chalk.
|
|
877
|
+
console.log(' project-root/');
|
|
878
|
+
console.log(' ├── _bmad/');
|
|
879
|
+
console.log(' │ ├── _config/');
|
|
880
|
+
console.log(` │ │ └── ${chalk.cyan('scopes.yaml')} ${chalk.dim('# Scope registry and settings')}`);
|
|
881
|
+
console.log(' │ └── _events/');
|
|
882
|
+
console.log(` │ ├── ${chalk.cyan('event-log.yaml')} ${chalk.dim('# Event history')}`);
|
|
883
|
+
console.log(` │ └── ${chalk.cyan('subscriptions.yaml')} ${chalk.dim('# Cross-scope subscriptions')}`);
|
|
884
|
+
console.log(' │');
|
|
885
|
+
console.log(' ├── _bmad-output/');
|
|
886
|
+
console.log(` │ ├── ${chalk.yellow('_shared/')} ${chalk.dim('# Shared knowledge layer')}`);
|
|
887
|
+
console.log(` │ │ ├── ${chalk.cyan('project-context.md')} ${chalk.dim('# Global project context')}`);
|
|
888
|
+
console.log(` │ │ ├── contracts/ ${chalk.dim('# Integration contracts')}`);
|
|
889
|
+
console.log(` │ │ └── principles/ ${chalk.dim('# Architecture principles')}`);
|
|
890
|
+
console.log(' │ │');
|
|
891
|
+
console.log(` │ ├── ${chalk.green('auth/')} ${chalk.dim('# Auth scope artifacts')}`);
|
|
892
|
+
console.log(' │ │ ├── planning-artifacts/');
|
|
893
|
+
console.log(' │ │ ├── implementation-artifacts/');
|
|
894
|
+
console.log(' │ │ ├── tests/');
|
|
895
|
+
console.log(` │ │ └── ${chalk.cyan('project-context.md')} ${chalk.dim('# Scope-specific context (optional)')}`);
|
|
896
|
+
console.log(' │ │');
|
|
897
|
+
console.log(` │ └── ${chalk.green('payments/')} ${chalk.dim('# Payments scope artifacts')}`);
|
|
898
|
+
console.log(' │ └── ...');
|
|
899
|
+
console.log(' │');
|
|
900
|
+
console.log(` └── ${chalk.cyan('.bmad-scope')} ${chalk.dim('# Session-sticky active scope (gitignored)')}`);
|
|
387
901
|
console.log();
|
|
902
|
+
|
|
903
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
904
|
+
|
|
905
|
+
// Access Model
|
|
906
|
+
console.log(chalk.bold(' ACCESS MODEL\n'));
|
|
907
|
+
console.log(' Scopes follow a "read-any, write-own" isolation model:\n');
|
|
908
|
+
console.log(' ┌─────────────┬─────────────────┬─────────────────┬─────────────┐');
|
|
909
|
+
console.log(' │ Operation │ Own Scope │ Other Scopes │ _shared/ │');
|
|
910
|
+
console.log(' ├─────────────┼─────────────────┼─────────────────┼─────────────┤');
|
|
911
|
+
console.log(
|
|
912
|
+
` │ ${chalk.green('Read')} │ ${chalk.green('✓ Allowed')} │ ${chalk.green('✓ Allowed')} │ ${chalk.green('✓ Allowed')} │`,
|
|
913
|
+
);
|
|
914
|
+
console.log(
|
|
915
|
+
` │ ${chalk.red('Write')} │ ${chalk.green('✓ Allowed')} │ ${chalk.red('✗ Blocked')} │ ${chalk.yellow('via sync-up')} │`,
|
|
916
|
+
);
|
|
917
|
+
console.log(' └─────────────┴─────────────────┴─────────────────┴─────────────┘\n');
|
|
918
|
+
|
|
919
|
+
console.log(chalk.dim(' Isolation modes (configure in scopes.yaml):'));
|
|
920
|
+
console.log(` • ${chalk.cyan('strict')} - Block cross-scope writes (default, recommended)`);
|
|
921
|
+
console.log(` • ${chalk.cyan('warn')} - Allow with warnings`);
|
|
922
|
+
console.log(` • ${chalk.cyan('permissive')} - Allow all (not recommended)\n`);
|
|
923
|
+
|
|
924
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
925
|
+
|
|
926
|
+
// Workflow Integration
|
|
927
|
+
console.log(chalk.bold(' WORKFLOW INTEGRATION\n'));
|
|
928
|
+
console.log(' Workflows automatically detect and use scope context:\n');
|
|
929
|
+
console.log(chalk.dim(' Resolution order:'));
|
|
930
|
+
console.log(' 1. Explicit --scope flag in workflow command');
|
|
931
|
+
console.log(' 2. Session context from .bmad-scope file');
|
|
932
|
+
console.log(' 3. BMAD_SCOPE environment variable');
|
|
933
|
+
console.log(' 4. Prompt user to select or create scope\n');
|
|
934
|
+
|
|
935
|
+
console.log(chalk.dim(' Scope-aware path variables in workflows:'));
|
|
936
|
+
console.log(` • ${chalk.cyan('{scope}')} → Scope ID (e.g., "auth")`);
|
|
937
|
+
console.log(` • ${chalk.cyan('{scope_path}')} → _bmad-output/auth`);
|
|
938
|
+
console.log(` • ${chalk.cyan('{scope_planning}')} → _bmad-output/auth/planning-artifacts`);
|
|
939
|
+
console.log(` • ${chalk.cyan('{scope_implementation}')} → _bmad-output/auth/implementation-artifacts`);
|
|
940
|
+
console.log(` • ${chalk.cyan('{scope_tests}')} → _bmad-output/auth/tests\n`);
|
|
941
|
+
|
|
942
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
943
|
+
|
|
944
|
+
// Use Cases
|
|
945
|
+
console.log(chalk.bold(' USE CASES\n'));
|
|
946
|
+
console.log(chalk.dim(' Multi-team projects:'));
|
|
947
|
+
console.log(' Each team creates their own scope. Shared contracts and architecture');
|
|
948
|
+
console.log(' principles live in _shared/ and are synced as needed.\n');
|
|
949
|
+
|
|
950
|
+
console.log(chalk.dim(' Microservices architecture:'));
|
|
951
|
+
console.log(' One scope per service. Use dependencies to track service relationships.');
|
|
952
|
+
console.log(' Contracts define APIs between services.\n');
|
|
953
|
+
|
|
954
|
+
console.log(chalk.dim(' Parallel feature development:'));
|
|
955
|
+
console.log(' Create scope per major feature. Develop PRD, architecture, and stories');
|
|
956
|
+
console.log(' independently, then merge to main codebase.\n');
|
|
957
|
+
|
|
958
|
+
console.log(chalk.dim(' Experimentation/Spikes:'));
|
|
959
|
+
console.log(' Create a scope for experiments. Archive or remove when done.\n');
|
|
960
|
+
|
|
961
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
962
|
+
|
|
963
|
+
// Troubleshooting
|
|
964
|
+
console.log(chalk.bold(' TROUBLESHOOTING\n'));
|
|
965
|
+
console.log(chalk.dim(' "Scope system not initialized":'));
|
|
966
|
+
console.log(` Run: ${chalk.cyan('npx bmad-fh scope init')}\n`);
|
|
967
|
+
|
|
968
|
+
console.log(chalk.dim(' "Cannot write to scope X while in scope Y":'));
|
|
969
|
+
console.log(' You are in strict isolation mode. Either:');
|
|
970
|
+
console.log(' • Switch to the correct scope');
|
|
971
|
+
console.log(' • Use sync-up to promote artifacts to _shared/');
|
|
972
|
+
console.log(' • Change isolation_mode in scopes.yaml (not recommended)\n');
|
|
973
|
+
|
|
974
|
+
console.log(chalk.dim(' "No scope set" when running workflow:'));
|
|
975
|
+
console.log(` • Create and use a scope: ${chalk.cyan('npx bmad-fh scope create myfeature')}`);
|
|
976
|
+
console.log(' • Or run workflow with --scope flag\n');
|
|
977
|
+
|
|
978
|
+
console.log(chalk.dim(' "Circular dependency detected":'));
|
|
979
|
+
console.log(' Scope A depends on B which depends on A. Remove one dependency.\n');
|
|
980
|
+
|
|
981
|
+
console.log(chalk.dim(' Debug mode:'));
|
|
982
|
+
console.log(` Set ${chalk.cyan('DEBUG=true')} environment variable for verbose output.\n`);
|
|
983
|
+
|
|
984
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────────────────────────────\n'));
|
|
985
|
+
|
|
986
|
+
// More Help
|
|
987
|
+
console.log(chalk.bold(' MORE HELP\n'));
|
|
988
|
+
console.log(` ${chalk.cyan('npx bmad-fh scope help init')} ${chalk.dim('# Detailed help for init command')}`);
|
|
989
|
+
console.log(` ${chalk.cyan('npx bmad-fh scope help create')} ${chalk.dim('# Detailed help for create command')}`);
|
|
990
|
+
console.log(` ${chalk.cyan('npx bmad-fh scope help sync-up')} ${chalk.dim('# Detailed help for sync operations')}\n`);
|
|
991
|
+
|
|
992
|
+
console.log(chalk.dim(' Documentation:'));
|
|
993
|
+
console.log(` • Multi-Scope Guide: ${chalk.cyan('docs/multi-scope-guide.md')}`);
|
|
994
|
+
console.log(` • Full docs: ${chalk.cyan('http://docs.bmad-method.org')}\n`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Show detailed help for 'init' subcommand
|
|
999
|
+
*/
|
|
1000
|
+
function showHelpInit() {
|
|
1001
|
+
console.log(chalk.bold('\n bmad scope init'));
|
|
1002
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1003
|
+
|
|
1004
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1005
|
+
console.log(' Initialize the multi-scope system in your project. This command creates the');
|
|
1006
|
+
console.log(' necessary configuration files and directory structure for scope management.\n');
|
|
1007
|
+
|
|
1008
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1009
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope init\n`);
|
|
1010
|
+
|
|
1011
|
+
console.log(chalk.bold(' WHAT IT CREATES\n'));
|
|
1012
|
+
console.log(` ${chalk.cyan('_bmad/_config/scopes.yaml')} Configuration file with scope registry`);
|
|
1013
|
+
console.log(` ${chalk.cyan('_bmad/_events/')} Event system directory`);
|
|
1014
|
+
console.log(` ${chalk.cyan('_bmad-output/_shared/')} Shared knowledge layer\n`);
|
|
1015
|
+
|
|
1016
|
+
console.log(chalk.bold(' NOTES\n'));
|
|
1017
|
+
console.log(' • Safe to run multiple times - will not overwrite existing config');
|
|
1018
|
+
console.log(' • Required before creating any scopes');
|
|
1019
|
+
console.log(' • Automatically run by "scope create" if not initialized\n');
|
|
1020
|
+
|
|
1021
|
+
console.log(chalk.bold(' EXAMPLE\n'));
|
|
1022
|
+
console.log(` ${chalk.green('$')} cd my-project`);
|
|
1023
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope init`);
|
|
1024
|
+
console.log(` ${chalk.dim('✓ Scope system initialized successfully!')}\n`);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Show detailed help for 'create' subcommand
|
|
1029
|
+
*/
|
|
1030
|
+
function showHelpCreate() {
|
|
1031
|
+
console.log(chalk.bold('\n bmad scope create'));
|
|
1032
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1033
|
+
|
|
1034
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1035
|
+
console.log(' Create a new isolated scope for parallel development. Each scope has its own');
|
|
1036
|
+
console.log(' directory structure for artifacts and can optionally declare dependencies on');
|
|
1037
|
+
console.log(' other scopes.\n');
|
|
1038
|
+
|
|
1039
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1040
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create [id] [options]\n`);
|
|
1041
|
+
|
|
1042
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1043
|
+
console.log(` ${chalk.cyan('id')} Scope identifier (lowercase letters, numbers, hyphens)`);
|
|
1044
|
+
console.log(' If omitted, you will be prompted interactively\n');
|
|
1045
|
+
|
|
1046
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
1047
|
+
console.log(` ${chalk.cyan('-n, --name')} ${chalk.dim('<name>')}`);
|
|
1048
|
+
console.log(' Human-readable name for the scope');
|
|
1049
|
+
console.log(' Example: --name "Authentication Service"\n');
|
|
1050
|
+
|
|
1051
|
+
console.log(` ${chalk.cyan('-d, --description')} ${chalk.dim('<text>')}`);
|
|
1052
|
+
console.log(' Brief description of the scope purpose');
|
|
1053
|
+
console.log(' Example: --description "Handles user auth, SSO, and sessions"\n');
|
|
1054
|
+
|
|
1055
|
+
console.log(` ${chalk.cyan('--deps, --dependencies')} ${chalk.dim('<ids>')}`);
|
|
1056
|
+
console.log(' Comma-separated list of scope IDs this scope depends on');
|
|
1057
|
+
console.log(' Example: --deps auth,users,notifications\n');
|
|
1058
|
+
|
|
1059
|
+
console.log(` ${chalk.cyan('--context')}`);
|
|
1060
|
+
console.log(' Create a scope-specific project-context.md file');
|
|
1061
|
+
console.log(' Useful when scope needs its own context extending global\n');
|
|
1062
|
+
|
|
1063
|
+
console.log(chalk.bold(' SCOPE ID RULES\n'));
|
|
1064
|
+
console.log(' • Lowercase letters, numbers, and hyphens only');
|
|
1065
|
+
console.log(' • Must start with a letter');
|
|
1066
|
+
console.log(' • Cannot use reserved names: _shared, _backup, _config, _events');
|
|
1067
|
+
console.log(' • Maximum 50 characters\n');
|
|
1068
|
+
|
|
1069
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1070
|
+
console.log(chalk.dim(' Interactive mode (prompts for all fields):'));
|
|
1071
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create\n`);
|
|
1072
|
+
|
|
1073
|
+
console.log(chalk.dim(' Quick create with ID only:'));
|
|
1074
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create auth\n`);
|
|
1075
|
+
|
|
1076
|
+
console.log(chalk.dim(' Full specification:'));
|
|
1077
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope create payments \\`);
|
|
1078
|
+
console.log(` --name "Payment Processing" \\`);
|
|
1079
|
+
console.log(` --description "Stripe integration, invoicing, subscriptions" \\`);
|
|
1080
|
+
console.log(` --deps auth,users \\`);
|
|
1081
|
+
console.log(` --context\n`);
|
|
1082
|
+
|
|
1083
|
+
console.log(chalk.bold(' WHAT IT CREATES\n'));
|
|
1084
|
+
console.log(' _bmad-output/{scope-id}/');
|
|
1085
|
+
console.log(' ├── planning-artifacts/ # PRDs, architecture docs');
|
|
1086
|
+
console.log(' ├── implementation-artifacts/ # Sprint status, stories');
|
|
1087
|
+
console.log(' ├── tests/ # Test artifacts');
|
|
1088
|
+
console.log(' └── project-context.md # If --context specified\n');
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Show detailed help for 'list' subcommand
|
|
1093
|
+
*/
|
|
1094
|
+
function showHelpList() {
|
|
1095
|
+
console.log(chalk.bold('\n bmad scope list'));
|
|
1096
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1097
|
+
|
|
1098
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1099
|
+
console.log(' List all scopes in the project with their status and metadata.\n');
|
|
1100
|
+
|
|
1101
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1102
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope list [options]`);
|
|
1103
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope ls [options] ${chalk.dim('# alias')}\n`);
|
|
1104
|
+
|
|
1105
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
1106
|
+
console.log(` ${chalk.cyan('-s, --status')} ${chalk.dim('<status>')}`);
|
|
1107
|
+
console.log(' Filter by scope status');
|
|
1108
|
+
console.log(' Values: active, archived\n');
|
|
1109
|
+
|
|
1110
|
+
console.log(chalk.bold(' OUTPUT COLUMNS\n'));
|
|
1111
|
+
console.log(` ${chalk.cyan('ID')} Scope identifier`);
|
|
1112
|
+
console.log(` ${chalk.cyan('Name')} Human-readable name`);
|
|
1113
|
+
console.log(` ${chalk.cyan('Status')} active or archived`);
|
|
1114
|
+
console.log(` ${chalk.cyan('Created')} Creation date\n`);
|
|
1115
|
+
|
|
1116
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1117
|
+
console.log(chalk.dim(' List all scopes:'));
|
|
1118
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope list\n`);
|
|
1119
|
+
|
|
1120
|
+
console.log(chalk.dim(' List only active scopes:'));
|
|
1121
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope list --status active\n`);
|
|
1122
|
+
|
|
1123
|
+
console.log(chalk.dim(' List archived scopes:'));
|
|
1124
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope ls -s archived\n`);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Show detailed help for 'info' subcommand
|
|
1129
|
+
*/
|
|
1130
|
+
function showHelpInfo() {
|
|
1131
|
+
console.log(chalk.bold('\n bmad scope info'));
|
|
1132
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1133
|
+
|
|
1134
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1135
|
+
console.log(' Display detailed information about a specific scope including paths,');
|
|
1136
|
+
console.log(' dependencies, dependents, and metadata.\n');
|
|
1137
|
+
|
|
1138
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1139
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope info <id>`);
|
|
1140
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope show <id> ${chalk.dim('# alias')}`);
|
|
1141
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope <id> ${chalk.dim('# shorthand')}\n`);
|
|
1142
|
+
|
|
1143
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1144
|
+
console.log(` ${chalk.cyan('id')} Scope identifier (required)\n`);
|
|
1145
|
+
|
|
1146
|
+
console.log(chalk.bold(' DISPLAYED INFORMATION\n'));
|
|
1147
|
+
console.log(' • Basic info: ID, name, description, status');
|
|
1148
|
+
console.log(' • Timestamps: Created, last activity');
|
|
1149
|
+
console.log(' • Metrics: Artifact count');
|
|
1150
|
+
console.log(' • Paths: Planning, implementation, tests directories');
|
|
1151
|
+
console.log(' • Dependencies: Scopes this scope depends on');
|
|
1152
|
+
console.log(' • Dependents: Scopes that depend on this scope\n');
|
|
1153
|
+
|
|
1154
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1155
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope info auth`);
|
|
1156
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope auth ${chalk.dim('# shorthand')}\n`);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Show detailed help for 'remove' subcommand
|
|
1161
|
+
*/
|
|
1162
|
+
function showHelpRemove() {
|
|
1163
|
+
console.log(chalk.bold('\n bmad scope remove'));
|
|
1164
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1165
|
+
|
|
1166
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1167
|
+
console.log(' Remove a scope and optionally its artifacts. By default, creates a backup');
|
|
1168
|
+
console.log(' before removal and prompts for confirmation.\n');
|
|
1169
|
+
|
|
1170
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1171
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope remove <id> [options]`);
|
|
1172
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope rm <id> ${chalk.dim('# alias')}`);
|
|
1173
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope delete <id> ${chalk.dim('# alias')}\n`);
|
|
1174
|
+
|
|
1175
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1176
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to remove (required)\n`);
|
|
1177
|
+
|
|
1178
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
1179
|
+
console.log(` ${chalk.cyan('-f, --force')}`);
|
|
1180
|
+
console.log(' Skip confirmation prompt\n');
|
|
1181
|
+
|
|
1182
|
+
console.log(` ${chalk.cyan('--no-backup')}`);
|
|
1183
|
+
console.log(' Do not create backup before removal');
|
|
1184
|
+
console.log(` ${chalk.red('Warning: Artifacts will be permanently deleted!')}\n`);
|
|
1185
|
+
|
|
1186
|
+
console.log(chalk.bold(' BACKUP LOCATION\n'));
|
|
1187
|
+
console.log(' Backups are created at: _bmad-output/_backup_{id}_{timestamp}/\n');
|
|
1188
|
+
|
|
1189
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1190
|
+
console.log(chalk.dim(' Safe removal (prompts, creates backup):'));
|
|
1191
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope remove auth\n`);
|
|
1192
|
+
|
|
1193
|
+
console.log(chalk.dim(' Force removal without prompt (still creates backup):'));
|
|
1194
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope rm auth --force\n`);
|
|
1195
|
+
|
|
1196
|
+
console.log(chalk.dim(' Permanent removal (no backup, no prompt):'));
|
|
1197
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope delete auth --force --no-backup\n`);
|
|
1198
|
+
|
|
1199
|
+
console.log(chalk.bold(' CONSIDERATIONS\n'));
|
|
1200
|
+
console.log(' • Check dependents first: scopes depending on this will have broken deps');
|
|
1201
|
+
console.log(' • Consider archiving instead if you might need artifacts later');
|
|
1202
|
+
console.log(' • Backup includes all scope artifacts but not _shared/ content\n');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Show detailed help for 'archive' subcommand
|
|
1207
|
+
*/
|
|
1208
|
+
function showHelpArchive() {
|
|
1209
|
+
console.log(chalk.bold('\n bmad scope archive'));
|
|
1210
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1211
|
+
|
|
1212
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1213
|
+
console.log(' Archive a scope. Archived scopes are excluded from default listings but');
|
|
1214
|
+
console.log(' retain all artifacts. Use this for completed features or paused work.\n');
|
|
1215
|
+
|
|
1216
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1217
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope archive <id>\n`);
|
|
1218
|
+
|
|
1219
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1220
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to archive (required)\n`);
|
|
1221
|
+
|
|
1222
|
+
console.log(chalk.bold(' BEHAVIOR\n'));
|
|
1223
|
+
console.log(' • Scope status changes to "archived"');
|
|
1224
|
+
console.log(' • Artifacts remain intact');
|
|
1225
|
+
console.log(' • Excluded from "scope list" (use --status archived to see)');
|
|
1226
|
+
console.log(' • Can be reactivated with "scope activate"\n');
|
|
1227
|
+
|
|
1228
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1229
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope archive auth`);
|
|
1230
|
+
console.log(` ${chalk.dim("✓ Scope 'auth' archived.")}\n`);
|
|
1231
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope list --status archived`);
|
|
1232
|
+
console.log(` ${chalk.dim('# Shows auth in archived list')}\n`);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Show detailed help for 'activate' subcommand
|
|
1237
|
+
*/
|
|
1238
|
+
function showHelpActivate() {
|
|
1239
|
+
console.log(chalk.bold('\n bmad scope activate'));
|
|
1240
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1241
|
+
|
|
1242
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1243
|
+
console.log(' Reactivate an archived scope. The scope will appear in default listings');
|
|
1244
|
+
console.log(' and can be used for workflows again.\n');
|
|
1245
|
+
|
|
1246
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1247
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope activate <id>\n`);
|
|
1248
|
+
|
|
1249
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1250
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to activate (required)\n`);
|
|
1251
|
+
|
|
1252
|
+
console.log(chalk.bold(' EXAMPLE\n'));
|
|
1253
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope activate auth`);
|
|
1254
|
+
console.log(` ${chalk.dim("✓ Scope 'auth' activated.")}\n`);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Show detailed help for 'sync-up' subcommand
|
|
1259
|
+
*/
|
|
1260
|
+
function showHelpSyncUp() {
|
|
1261
|
+
console.log(chalk.bold('\n bmad scope sync-up'));
|
|
1262
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1263
|
+
|
|
1264
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1265
|
+
console.log(' Promote scope artifacts to the shared knowledge layer (_shared/). Use this');
|
|
1266
|
+
console.log(' to share mature artifacts like architecture decisions, contracts, and');
|
|
1267
|
+
console.log(' principles with other scopes.\n');
|
|
1268
|
+
|
|
1269
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1270
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-up <id> [options]\n`);
|
|
1271
|
+
|
|
1272
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1273
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to sync from (required)\n`);
|
|
1274
|
+
|
|
1275
|
+
console.log(chalk.bold(' WHAT GETS PROMOTED\n'));
|
|
1276
|
+
console.log(' • architecture/*.md → _shared/architecture/');
|
|
1277
|
+
console.log(' • contracts/*.md → _shared/contracts/');
|
|
1278
|
+
console.log(' • principles/*.md → _shared/principles/');
|
|
1279
|
+
console.log(' • project-context.md → Merged into _shared/project-context.md\n');
|
|
1280
|
+
|
|
1281
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
1282
|
+
console.log(` ${chalk.cyan('--dry-run')}`);
|
|
1283
|
+
console.log(' Show what would be promoted without making changes\n');
|
|
1284
|
+
|
|
1285
|
+
console.log(` ${chalk.cyan('--resolution')} ${chalk.dim('<strategy>')}`);
|
|
1286
|
+
console.log(' How to handle conflicts:');
|
|
1287
|
+
console.log(' • keep-local - Keep scope version');
|
|
1288
|
+
console.log(' • keep-shared - Keep shared version');
|
|
1289
|
+
console.log(' • backup-and-update - Backup shared, use scope version\n');
|
|
1290
|
+
|
|
1291
|
+
console.log(chalk.bold(' EXAMPLE\n'));
|
|
1292
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-up auth`);
|
|
1293
|
+
console.log(` ${chalk.dim('Promoted 3 files to _shared/')}`);
|
|
1294
|
+
console.log(` ${chalk.dim(' architecture/auth-design.md')}`);
|
|
1295
|
+
console.log(` ${chalk.dim(' contracts/auth-api.md')}`);
|
|
1296
|
+
console.log(` ${chalk.dim(' principles/security.md')}\n`);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Show detailed help for 'sync-down' subcommand
|
|
1301
|
+
*/
|
|
1302
|
+
function showHelpSyncDown() {
|
|
1303
|
+
console.log(chalk.bold('\n bmad scope sync-down'));
|
|
1304
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1305
|
+
|
|
1306
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1307
|
+
console.log(' Pull updates from the shared knowledge layer into a scope. Use this to get');
|
|
1308
|
+
console.log(' the latest shared architecture, contracts, and context into your scope.\n');
|
|
1309
|
+
|
|
1310
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1311
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-down <id> [options]\n`);
|
|
1312
|
+
|
|
1313
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1314
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to sync to (required)\n`);
|
|
1315
|
+
|
|
1316
|
+
console.log(chalk.bold(' OPTIONS\n'));
|
|
1317
|
+
console.log(` ${chalk.cyan('--dry-run')}`);
|
|
1318
|
+
console.log(' Show what would be pulled without making changes\n');
|
|
1319
|
+
|
|
1320
|
+
console.log(` ${chalk.cyan('--resolution')} ${chalk.dim('<strategy>')}`);
|
|
1321
|
+
console.log(' How to handle conflicts:');
|
|
1322
|
+
console.log(' • keep-local - Keep scope version (default)');
|
|
1323
|
+
console.log(' • keep-shared - Overwrite with shared version');
|
|
1324
|
+
console.log(' • backup-and-update - Backup scope, use shared version\n');
|
|
1325
|
+
|
|
1326
|
+
console.log(chalk.bold(' EXAMPLE\n'));
|
|
1327
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope sync-down payments`);
|
|
1328
|
+
console.log(` ${chalk.dim('Pulled 2 updates from _shared/')}`);
|
|
1329
|
+
console.log(` ${chalk.dim(' contracts/auth-api.md (new)')}`);
|
|
1330
|
+
console.log(` ${chalk.dim(' project-context.md (merged)')}\n`);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Show detailed help for 'set' subcommand
|
|
1335
|
+
*/
|
|
1336
|
+
function showHelpSet() {
|
|
1337
|
+
console.log(chalk.bold('\n bmad scope set'));
|
|
1338
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1339
|
+
|
|
1340
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1341
|
+
console.log(' Set the active scope for your session. This creates a .bmad-scope file in');
|
|
1342
|
+
console.log(' your project root that workflows automatically detect and use.\n');
|
|
1343
|
+
|
|
1344
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1345
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope set [id]`);
|
|
1346
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope use [id] ${chalk.dim('# alias')}\n`);
|
|
1347
|
+
|
|
1348
|
+
console.log(chalk.bold(' ARGUMENTS\n'));
|
|
1349
|
+
console.log(` ${chalk.cyan('id')} Scope identifier to set as active (optional)`);
|
|
1350
|
+
console.log(' If omitted, shows current scope and prompts to select\n');
|
|
1351
|
+
|
|
1352
|
+
console.log(chalk.bold(' BEHAVIOR\n'));
|
|
1353
|
+
console.log(' • Creates/updates .bmad-scope file in project root');
|
|
1354
|
+
console.log(' • .bmad-scope should be added to .gitignore (session-specific)');
|
|
1355
|
+
console.log(' • Workflows automatically detect scope from this file');
|
|
1356
|
+
console.log(' • BMAD_SCOPE environment variable can override\n');
|
|
1357
|
+
|
|
1358
|
+
console.log(chalk.bold(' EXAMPLES\n'));
|
|
1359
|
+
console.log(chalk.dim(' Set a specific scope:'));
|
|
1360
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope set auth\n`);
|
|
1361
|
+
|
|
1362
|
+
console.log(chalk.dim(' Interactive selection (shows current and prompts):'));
|
|
1363
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope set\n`);
|
|
1364
|
+
|
|
1365
|
+
console.log(chalk.dim(' Override with environment variable:'));
|
|
1366
|
+
console.log(` ${chalk.green('$')} BMAD_SCOPE=payments npx bmad-fh ...\n`);
|
|
1367
|
+
|
|
1368
|
+
console.log(chalk.bold(' FILE FORMAT\n'));
|
|
1369
|
+
console.log(' The .bmad-scope file contains:');
|
|
1370
|
+
console.log(chalk.dim(' active_scope: auth'));
|
|
1371
|
+
console.log(chalk.dim(' set_at: "2026-01-22T10:00:00Z"\n'));
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* Show detailed help for 'unset' subcommand
|
|
1376
|
+
*/
|
|
1377
|
+
function showHelpUnset() {
|
|
1378
|
+
console.log(chalk.bold('\n bmad scope unset'));
|
|
1379
|
+
console.log(chalk.dim(' ═══════════════════════════════════════════════════════════════════════════\n'));
|
|
1380
|
+
|
|
1381
|
+
console.log(chalk.bold(' DESCRIPTION\n'));
|
|
1382
|
+
console.log(' Clear the active scope by removing the .bmad-scope file. After this,');
|
|
1383
|
+
console.log(' workflows will prompt for scope selection.\n');
|
|
1384
|
+
|
|
1385
|
+
console.log(chalk.bold(' USAGE\n'));
|
|
1386
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope unset`);
|
|
1387
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope clear ${chalk.dim('# alias')}\n`);
|
|
1388
|
+
|
|
1389
|
+
console.log(chalk.bold(' BEHAVIOR\n'));
|
|
1390
|
+
console.log(' • Removes .bmad-scope file from project root');
|
|
1391
|
+
console.log(' • Workflows will prompt for scope selection');
|
|
1392
|
+
console.log(' • Does nothing if no scope is currently set\n');
|
|
1393
|
+
|
|
1394
|
+
console.log(chalk.bold(' EXAMPLE\n'));
|
|
1395
|
+
console.log(` ${chalk.green('$')} npx bmad-fh scope unset`);
|
|
1396
|
+
console.log(` ${chalk.dim('✓ Active scope cleared.')}\n`);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Router for subcommand-specific help
|
|
1401
|
+
* @param {string} subcommand - The subcommand to show help for
|
|
1402
|
+
*/
|
|
1403
|
+
function showSubcommandHelp(subcommand) {
|
|
1404
|
+
const helpFunctions = {
|
|
1405
|
+
init: showHelpInit,
|
|
1406
|
+
create: showHelpCreate,
|
|
1407
|
+
new: showHelpCreate,
|
|
1408
|
+
list: showHelpList,
|
|
1409
|
+
ls: showHelpList,
|
|
1410
|
+
info: showHelpInfo,
|
|
1411
|
+
show: showHelpInfo,
|
|
1412
|
+
remove: showHelpRemove,
|
|
1413
|
+
rm: showHelpRemove,
|
|
1414
|
+
delete: showHelpRemove,
|
|
1415
|
+
archive: showHelpArchive,
|
|
1416
|
+
activate: showHelpActivate,
|
|
1417
|
+
set: showHelpSet,
|
|
1418
|
+
use: showHelpSet,
|
|
1419
|
+
unset: showHelpUnset,
|
|
1420
|
+
clear: showHelpUnset,
|
|
1421
|
+
'sync-up': showHelpSyncUp,
|
|
1422
|
+
syncup: showHelpSyncUp,
|
|
1423
|
+
'sync-down': showHelpSyncDown,
|
|
1424
|
+
syncdown: showHelpSyncDown,
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
if (helpFunctions[subcommand]) {
|
|
1428
|
+
helpFunctions[subcommand]();
|
|
1429
|
+
} else {
|
|
1430
|
+
console.log(chalk.red(`\n Unknown command: ${subcommand}\n`));
|
|
1431
|
+
console.log(` Run ${chalk.cyan('npx bmad-fh scope help')} to see available commands.\n`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Generate help text string for Commander.js
|
|
1437
|
+
* This is called when --help is used
|
|
1438
|
+
*/
|
|
1439
|
+
function getHelpText() {
|
|
1440
|
+
const lines = [
|
|
1441
|
+
'',
|
|
1442
|
+
chalk.bold('SUBCOMMANDS'),
|
|
1443
|
+
'',
|
|
1444
|
+
` ${chalk.cyan('init')} Initialize scope system in current project`,
|
|
1445
|
+
` ${chalk.cyan('list')} ${chalk.dim('[options]')} List all scopes (aliases: ls)`,
|
|
1446
|
+
` ${chalk.cyan('create')} ${chalk.dim('[id] [opts]')} Create a new scope (aliases: new)`,
|
|
1447
|
+
` ${chalk.cyan('info')} ${chalk.dim('<id>')} Show detailed scope information (aliases: show)`,
|
|
1448
|
+
` ${chalk.cyan('remove')} ${chalk.dim('<id> [opts]')} Remove a scope and its artifacts (aliases: rm, delete)`,
|
|
1449
|
+
` ${chalk.cyan('archive')} ${chalk.dim('<id>')} Archive a scope (preserves artifacts)`,
|
|
1450
|
+
` ${chalk.cyan('activate')} ${chalk.dim('<id>')} Reactivate an archived scope`,
|
|
1451
|
+
` ${chalk.cyan('set')} ${chalk.dim('[id]')} Set active scope for session (alias: use)`,
|
|
1452
|
+
` ${chalk.cyan('unset')} Clear active scope (alias: clear)`,
|
|
1453
|
+
` ${chalk.cyan('sync-up')} ${chalk.dim('<id> [opts]')} Promote scope artifacts to shared layer`,
|
|
1454
|
+
` ${chalk.cyan('sync-down')} ${chalk.dim('<id> [opts]')} Pull shared layer updates into scope`,
|
|
1455
|
+
` ${chalk.cyan('help')} ${chalk.dim('[command]')} Show detailed help for a command`,
|
|
1456
|
+
'',
|
|
1457
|
+
chalk.bold('QUICK START'),
|
|
1458
|
+
'',
|
|
1459
|
+
` ${chalk.green('$')} npx bmad-fh scope init`,
|
|
1460
|
+
` ${chalk.green('$')} npx bmad-fh scope create auth --name "Auth Service"`,
|
|
1461
|
+
` ${chalk.green('$')} npx bmad-fh scope set auth`,
|
|
1462
|
+
'',
|
|
1463
|
+
chalk.bold('MORE HELP'),
|
|
1464
|
+
'',
|
|
1465
|
+
` ${chalk.cyan('npx bmad-fh scope help')} Show comprehensive documentation`,
|
|
1466
|
+
` ${chalk.cyan('npx bmad-fh scope help <cmd>')} Show detailed help for a subcommand`,
|
|
1467
|
+
'',
|
|
1468
|
+
];
|
|
1469
|
+
|
|
1470
|
+
return lines.join('\n');
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Configure the Commander command with custom help
|
|
1475
|
+
* @param {import('commander').Command} command - The Commander command instance
|
|
1476
|
+
*/
|
|
1477
|
+
function configureCommand(command) {
|
|
1478
|
+
// Add custom help text after the auto-generated options
|
|
1479
|
+
command.addHelpText('after', getHelpText);
|
|
1480
|
+
|
|
1481
|
+
// Show help after errors to guide users
|
|
1482
|
+
command.showHelpAfterError('(use --help for available subcommands)');
|
|
388
1483
|
}
|
|
389
1484
|
|
|
390
1485
|
module.exports = {
|
|
391
1486
|
command: 'scope [subcommand] [id]',
|
|
392
1487
|
description: 'Manage scopes for parallel artifact isolation',
|
|
1488
|
+
configureCommand,
|
|
393
1489
|
options: [
|
|
394
1490
|
['-n, --name <name>', 'Scope name (for create)'],
|
|
395
1491
|
['-d, --description <desc>', 'Scope description'],
|
|
@@ -397,61 +1493,100 @@ module.exports = {
|
|
|
397
1493
|
['-f, --force', 'Force operation without confirmation'],
|
|
398
1494
|
['--no-backup', 'Skip backup on remove'],
|
|
399
1495
|
['--context', 'Create scope-specific project-context.md'],
|
|
400
|
-
['-s, --status <status>', 'Filter by status (active/archived)']
|
|
1496
|
+
['-s, --status <status>', 'Filter by status (active/archived)'],
|
|
1497
|
+
['--dry-run', 'Show what would be synced without making changes'],
|
|
1498
|
+
['--resolution <strategy>', 'Conflict resolution: keep-local|keep-shared|backup-and-update'],
|
|
401
1499
|
],
|
|
1500
|
+
// Export help functions for testing
|
|
1501
|
+
showHelp,
|
|
1502
|
+
showSubcommandHelp,
|
|
1503
|
+
getHelpText,
|
|
402
1504
|
action: async (subcommand, id, options) => {
|
|
403
1505
|
try {
|
|
404
1506
|
// Determine project root
|
|
405
1507
|
const projectRoot = process.cwd();
|
|
406
|
-
|
|
1508
|
+
|
|
407
1509
|
// Handle subcommands
|
|
408
1510
|
switch (subcommand) {
|
|
409
1511
|
case 'init': {
|
|
410
1512
|
await handleInit(projectRoot);
|
|
411
1513
|
break;
|
|
412
1514
|
}
|
|
413
|
-
|
|
1515
|
+
|
|
414
1516
|
case 'list':
|
|
415
1517
|
case 'ls': {
|
|
416
1518
|
await handleList(projectRoot, options);
|
|
417
1519
|
break;
|
|
418
1520
|
}
|
|
419
|
-
|
|
1521
|
+
|
|
420
1522
|
case 'create':
|
|
421
1523
|
case 'new': {
|
|
422
1524
|
await handleCreate(projectRoot, id, options);
|
|
423
1525
|
break;
|
|
424
1526
|
}
|
|
425
|
-
|
|
1527
|
+
|
|
426
1528
|
case 'info':
|
|
427
1529
|
case 'show': {
|
|
428
1530
|
await handleInfo(projectRoot, id);
|
|
429
1531
|
break;
|
|
430
1532
|
}
|
|
431
|
-
|
|
1533
|
+
|
|
432
1534
|
case 'remove':
|
|
433
1535
|
case 'rm':
|
|
434
1536
|
case 'delete': {
|
|
435
1537
|
await handleRemove(projectRoot, id, options);
|
|
436
1538
|
break;
|
|
437
1539
|
}
|
|
438
|
-
|
|
1540
|
+
|
|
439
1541
|
case 'archive': {
|
|
440
1542
|
await handleArchive(projectRoot, id);
|
|
441
1543
|
break;
|
|
442
1544
|
}
|
|
443
|
-
|
|
1545
|
+
|
|
444
1546
|
case 'activate': {
|
|
445
1547
|
await handleActivate(projectRoot, id);
|
|
446
1548
|
break;
|
|
447
1549
|
}
|
|
448
|
-
|
|
449
|
-
case '
|
|
1550
|
+
|
|
1551
|
+
case 'sync-up':
|
|
1552
|
+
case 'syncup': {
|
|
1553
|
+
await handleSyncUp(projectRoot, id, options);
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
case 'sync-down':
|
|
1558
|
+
case 'syncdown': {
|
|
1559
|
+
await handleSyncDown(projectRoot, id, options);
|
|
1560
|
+
break;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
case 'set':
|
|
1564
|
+
case 'use': {
|
|
1565
|
+
await handleSet(projectRoot, id, options);
|
|
1566
|
+
break;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
case 'unset':
|
|
1570
|
+
case 'clear': {
|
|
1571
|
+
await handleUnset(projectRoot);
|
|
1572
|
+
break;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
case 'help': {
|
|
1576
|
+
// Check if a subcommand was provided for detailed help
|
|
1577
|
+
if (id) {
|
|
1578
|
+
showSubcommandHelp(id);
|
|
1579
|
+
} else {
|
|
1580
|
+
showHelp();
|
|
1581
|
+
}
|
|
1582
|
+
break;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
450
1585
|
case undefined: {
|
|
451
1586
|
showHelp();
|
|
452
1587
|
break;
|
|
453
1588
|
}
|
|
454
|
-
|
|
1589
|
+
|
|
455
1590
|
default: {
|
|
456
1591
|
// If subcommand looks like an ID, show info for it
|
|
457
1592
|
if (subcommand && !subcommand.startsWith('-')) {
|
|
@@ -461,7 +1596,7 @@ module.exports = {
|
|
|
461
1596
|
}
|
|
462
1597
|
}
|
|
463
1598
|
}
|
|
464
|
-
|
|
1599
|
+
|
|
465
1600
|
process.exit(0);
|
|
466
1601
|
} catch (error) {
|
|
467
1602
|
console.error(chalk.red(`\nError: ${error.message}`));
|
|
@@ -470,5 +1605,5 @@ module.exports = {
|
|
|
470
1605
|
}
|
|
471
1606
|
process.exit(1);
|
|
472
1607
|
}
|
|
473
|
-
}
|
|
1608
|
+
},
|
|
474
1609
|
};
|