bmad-fh 6.0.0-alpha.23.02a963fa → 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.
@@ -0,0 +1,1306 @@
1
+ /**
2
+ * Scope CLI Test Suite
3
+ *
4
+ * Comprehensive tests for the scope CLI command including:
5
+ * - All subcommands (init, create, list, info, set, unset, remove, archive, activate, sync-up, sync-down)
6
+ * - Help system (main help and subcommand-specific help)
7
+ * - Error handling and edge cases
8
+ * - Integration with ScopeManager, ScopeSync, and other components
9
+ *
10
+ * Usage: node test/test-scope-cli.js
11
+ * Exit codes: 0 = all tests pass, 1 = test failures
12
+ */
13
+
14
+ const fs = require('fs-extra');
15
+ const path = require('node:path');
16
+ const os = require('node:os');
17
+ const { execSync, spawn } = require('node:child_process');
18
+
19
+ // ANSI color codes
20
+ const colors = {
21
+ reset: '\u001B[0m',
22
+ green: '\u001B[32m',
23
+ red: '\u001B[31m',
24
+ yellow: '\u001B[33m',
25
+ blue: '\u001B[34m',
26
+ cyan: '\u001B[36m',
27
+ dim: '\u001B[2m',
28
+ bold: '\u001B[1m',
29
+ };
30
+
31
+ // Test utilities
32
+ let testCount = 0;
33
+ let passCount = 0;
34
+ let failCount = 0;
35
+ let skipCount = 0;
36
+ const failures = [];
37
+
38
+ function test(name, fn) {
39
+ testCount++;
40
+ try {
41
+ fn();
42
+ passCount++;
43
+ console.log(` ${colors.green}✓${colors.reset} ${name}`);
44
+ } catch (error) {
45
+ failCount++;
46
+ console.log(` ${colors.red}✗${colors.reset} ${name}`);
47
+ console.log(` ${colors.red}${error.message}${colors.reset}`);
48
+ failures.push({ name, error: error.message });
49
+ }
50
+ }
51
+
52
+ async function testAsync(name, fn) {
53
+ testCount++;
54
+ try {
55
+ await fn();
56
+ passCount++;
57
+ console.log(` ${colors.green}✓${colors.reset} ${name}`);
58
+ } catch (error) {
59
+ failCount++;
60
+ console.log(` ${colors.red}✗${colors.reset} ${name}`);
61
+ console.log(` ${colors.red}${error.message}${colors.reset}`);
62
+ failures.push({ name, error: error.message });
63
+ }
64
+ }
65
+
66
+ function skip(name, reason = '') {
67
+ skipCount++;
68
+ console.log(` ${colors.yellow}○${colors.reset} ${name} ${colors.dim}(skipped${reason ? ': ' + reason : ''})${colors.reset}`);
69
+ }
70
+
71
+ function assertEqual(actual, expected, message = '') {
72
+ if (actual !== expected) {
73
+ throw new Error(`${message}\n Expected: ${JSON.stringify(expected)}\n Actual: ${JSON.stringify(actual)}`);
74
+ }
75
+ }
76
+
77
+ function assertTrue(value, message = 'Expected true') {
78
+ if (!value) {
79
+ throw new Error(message);
80
+ }
81
+ }
82
+
83
+ function assertFalse(value, message = 'Expected false') {
84
+ if (value) {
85
+ throw new Error(message);
86
+ }
87
+ }
88
+
89
+ function assertContains(str, substring, message = '') {
90
+ if (!str.includes(substring)) {
91
+ throw new Error(`${message}\n Expected to contain: "${substring}"\n Actual: "${str.slice(0, 200)}..."`);
92
+ }
93
+ }
94
+
95
+ function assertNotContains(str, substring, message = '') {
96
+ if (str.includes(substring)) {
97
+ throw new Error(`${message}\n Expected NOT to contain: "${substring}"`);
98
+ }
99
+ }
100
+
101
+ function assertExists(filePath, message = '') {
102
+ if (!fs.existsSync(filePath)) {
103
+ throw new Error(`${message || 'File does not exist'}: ${filePath}`);
104
+ }
105
+ }
106
+
107
+ function assertNotExists(filePath, message = '') {
108
+ if (fs.existsSync(filePath)) {
109
+ throw new Error(`${message || 'File should not exist'}: ${filePath}`);
110
+ }
111
+ }
112
+
113
+ // Create temporary test directory with BMAD structure
114
+ function createTestProject() {
115
+ const tmpDir = path.join(os.tmpdir(), `bmad-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
116
+ fs.mkdirSync(tmpDir, { recursive: true });
117
+
118
+ // Create minimal BMAD structure
119
+ fs.mkdirSync(path.join(tmpDir, '_bmad', '_config'), { recursive: true });
120
+ fs.mkdirSync(path.join(tmpDir, '_bmad-output'), { recursive: true });
121
+
122
+ return tmpDir;
123
+ }
124
+
125
+ function cleanupTestProject(tmpDir) {
126
+ try {
127
+ fs.rmSync(tmpDir, { recursive: true, force: true });
128
+ } catch {
129
+ // Ignore cleanup errors
130
+ }
131
+ }
132
+
133
+ // Get path to CLI
134
+ const CLI_PATH = path.join(__dirname, '..', 'tools', 'cli', 'bmad-cli.js');
135
+
136
+ // Execute CLI command and capture output
137
+ function runCli(args, cwd, options = {}) {
138
+ const cmd = `node "${CLI_PATH}" ${args}`;
139
+ try {
140
+ const output = execSync(cmd, {
141
+ cwd,
142
+ encoding: 'utf8',
143
+ timeout: options.timeout || 30_000,
144
+ env: { ...process.env, ...options.env, FORCE_COLOR: '0' },
145
+ });
146
+ return { success: true, output, exitCode: 0 };
147
+ } catch (error) {
148
+ return {
149
+ success: false,
150
+ output: error.stdout || '',
151
+ stderr: error.stderr || '',
152
+ exitCode: error.status || 1,
153
+ error: error.message,
154
+ };
155
+ }
156
+ }
157
+
158
+ // ============================================================================
159
+ // Help System Tests
160
+ // ============================================================================
161
+
162
+ function testHelpSystem() {
163
+ console.log(`\n${colors.blue}${colors.bold}Help System Tests${colors.reset}`);
164
+
165
+ const tmpDir = createTestProject();
166
+
167
+ try {
168
+ // Main help
169
+ test('scope help shows overview', () => {
170
+ const result = runCli('scope help', tmpDir);
171
+ assertContains(result.output, 'BMAD Scope Management');
172
+ assertContains(result.output, 'OVERVIEW');
173
+ assertContains(result.output, 'COMMANDS');
174
+ });
175
+
176
+ test('scope help shows all commands', () => {
177
+ const result = runCli('scope help', tmpDir);
178
+ assertContains(result.output, 'init');
179
+ assertContains(result.output, 'list');
180
+ assertContains(result.output, 'create');
181
+ assertContains(result.output, 'info');
182
+ assertContains(result.output, 'set');
183
+ assertContains(result.output, 'unset');
184
+ assertContains(result.output, 'remove');
185
+ assertContains(result.output, 'archive');
186
+ assertContains(result.output, 'activate');
187
+ assertContains(result.output, 'sync-up');
188
+ assertContains(result.output, 'sync-down');
189
+ });
190
+
191
+ test('scope help shows options', () => {
192
+ const result = runCli('scope help', tmpDir);
193
+ assertContains(result.output, 'OPTIONS');
194
+ assertContains(result.output, '--name');
195
+ assertContains(result.output, '--description');
196
+ assertContains(result.output, '--force');
197
+ assertContains(result.output, '--dry-run');
198
+ assertContains(result.output, '--resolution');
199
+ });
200
+
201
+ test('scope help shows quick start', () => {
202
+ const result = runCli('scope help', tmpDir);
203
+ assertContains(result.output, 'QUICK START');
204
+ assertContains(result.output, 'scope init');
205
+ assertContains(result.output, 'scope create');
206
+ assertContains(result.output, 'scope set');
207
+ });
208
+
209
+ test('scope help shows directory structure', () => {
210
+ const result = runCli('scope help', tmpDir);
211
+ assertContains(result.output, 'DIRECTORY STRUCTURE');
212
+ assertContains(result.output, '_bmad-output');
213
+ assertContains(result.output, '_shared');
214
+ assertContains(result.output, 'scopes.yaml');
215
+ });
216
+
217
+ test('scope help shows access model', () => {
218
+ const result = runCli('scope help', tmpDir);
219
+ assertContains(result.output, 'ACCESS MODEL');
220
+ assertContains(result.output, 'read-any');
221
+ assertContains(result.output, 'write-own');
222
+ });
223
+
224
+ test('scope help shows troubleshooting', () => {
225
+ const result = runCli('scope help', tmpDir);
226
+ assertContains(result.output, 'TROUBLESHOOTING');
227
+ });
228
+
229
+ // Subcommand-specific help
230
+ test('scope help init shows detailed help', () => {
231
+ const result = runCli('scope help init', tmpDir);
232
+ assertContains(result.output, 'bmad scope init');
233
+ assertContains(result.output, 'DESCRIPTION');
234
+ assertContains(result.output, 'USAGE');
235
+ assertContains(result.output, 'WHAT IT CREATES');
236
+ });
237
+
238
+ test('scope help create shows detailed help', () => {
239
+ const result = runCli('scope help create', tmpDir);
240
+ assertContains(result.output, 'bmad scope create');
241
+ assertContains(result.output, 'ARGUMENTS');
242
+ assertContains(result.output, 'OPTIONS');
243
+ assertContains(result.output, '--name');
244
+ assertContains(result.output, '--deps');
245
+ assertContains(result.output, 'SCOPE ID RULES');
246
+ });
247
+
248
+ test('scope help list shows detailed help', () => {
249
+ const result = runCli('scope help list', tmpDir);
250
+ assertContains(result.output, 'bmad scope list');
251
+ assertContains(result.output, '--status');
252
+ assertContains(result.output, 'OUTPUT COLUMNS');
253
+ });
254
+
255
+ test('scope help info shows detailed help', () => {
256
+ const result = runCli('scope help info', tmpDir);
257
+ assertContains(result.output, 'bmad scope info');
258
+ assertContains(result.output, 'DISPLAYED INFORMATION');
259
+ });
260
+
261
+ test('scope help set shows detailed help', () => {
262
+ const result = runCli('scope help set', tmpDir);
263
+ assertContains(result.output, 'bmad scope set');
264
+ assertContains(result.output, '.bmad-scope');
265
+ assertContains(result.output, 'BMAD_SCOPE');
266
+ assertContains(result.output, 'FILE FORMAT');
267
+ });
268
+
269
+ test('scope help unset shows detailed help', () => {
270
+ const result = runCli('scope help unset', tmpDir);
271
+ assertContains(result.output, 'bmad scope unset');
272
+ assertContains(result.output, 'Clear');
273
+ });
274
+
275
+ test('scope help remove shows detailed help', () => {
276
+ const result = runCli('scope help remove', tmpDir);
277
+ assertContains(result.output, 'bmad scope remove');
278
+ assertContains(result.output, '--force');
279
+ assertContains(result.output, '--no-backup');
280
+ assertContains(result.output, 'BACKUP LOCATION');
281
+ });
282
+
283
+ test('scope help archive shows detailed help', () => {
284
+ const result = runCli('scope help archive', tmpDir);
285
+ assertContains(result.output, 'bmad scope archive');
286
+ assertContains(result.output, 'BEHAVIOR');
287
+ });
288
+
289
+ test('scope help activate shows detailed help', () => {
290
+ const result = runCli('scope help activate', tmpDir);
291
+ assertContains(result.output, 'bmad scope activate');
292
+ assertContains(result.output, 'Reactivate');
293
+ });
294
+
295
+ test('scope help sync-up shows detailed help', () => {
296
+ const result = runCli('scope help sync-up', tmpDir);
297
+ assertContains(result.output, 'bmad scope sync-up');
298
+ assertContains(result.output, 'WHAT GETS PROMOTED');
299
+ assertContains(result.output, '--dry-run');
300
+ assertContains(result.output, '--resolution');
301
+ });
302
+
303
+ test('scope help sync-down shows detailed help', () => {
304
+ const result = runCli('scope help sync-down', tmpDir);
305
+ assertContains(result.output, 'bmad scope sync-down');
306
+ assertContains(result.output, '--dry-run');
307
+ assertContains(result.output, 'keep-local');
308
+ assertContains(result.output, 'keep-shared');
309
+ });
310
+
311
+ // Alias help
312
+ test('scope help ls shows list help', () => {
313
+ const result = runCli('scope help ls', tmpDir);
314
+ assertContains(result.output, 'bmad scope list');
315
+ });
316
+
317
+ test('scope help use shows set help', () => {
318
+ const result = runCli('scope help use', tmpDir);
319
+ assertContains(result.output, 'bmad scope set');
320
+ });
321
+
322
+ test('scope help clear shows unset help', () => {
323
+ const result = runCli('scope help clear', tmpDir);
324
+ assertContains(result.output, 'bmad scope unset');
325
+ });
326
+
327
+ test('scope help rm shows remove help', () => {
328
+ const result = runCli('scope help rm', tmpDir);
329
+ assertContains(result.output, 'bmad scope remove');
330
+ });
331
+
332
+ test('scope help syncup shows sync-up help', () => {
333
+ const result = runCli('scope help syncup', tmpDir);
334
+ assertContains(result.output, 'bmad scope sync-up');
335
+ });
336
+
337
+ // Unknown command help
338
+ test('scope help unknown-cmd shows error', () => {
339
+ const result = runCli('scope help foobar', tmpDir);
340
+ assertContains(result.output, 'Unknown command');
341
+ assertContains(result.output, 'foobar');
342
+ });
343
+
344
+ // No args shows help
345
+ test('scope with no args shows help', () => {
346
+ const result = runCli('scope', tmpDir);
347
+ assertContains(result.output, 'BMAD Scope Management');
348
+ });
349
+ } finally {
350
+ cleanupTestProject(tmpDir);
351
+ }
352
+ }
353
+
354
+ // ============================================================================
355
+ // Init Command Tests
356
+ // ============================================================================
357
+
358
+ function testInitCommand() {
359
+ console.log(`\n${colors.blue}${colors.bold}Init Command Tests${colors.reset}`);
360
+
361
+ test('scope init creates configuration', () => {
362
+ const tmpDir = createTestProject();
363
+ try {
364
+ const result = runCli('scope init', tmpDir);
365
+ assertTrue(result.success, `Init should succeed: ${result.stderr || result.error}`);
366
+ assertContains(result.output, 'initialized successfully');
367
+
368
+ // Check files created
369
+ assertExists(path.join(tmpDir, '_bmad', '_config', 'scopes.yaml'));
370
+ assertExists(path.join(tmpDir, '_bmad-output', '_shared'));
371
+ assertExists(path.join(tmpDir, '_bmad', '_events'));
372
+ } finally {
373
+ cleanupTestProject(tmpDir);
374
+ }
375
+ });
376
+
377
+ test('scope init is idempotent', () => {
378
+ const tmpDir = createTestProject();
379
+ try {
380
+ // Run init twice
381
+ runCli('scope init', tmpDir);
382
+ const result = runCli('scope init', tmpDir);
383
+ assertTrue(result.success, 'Second init should succeed');
384
+ } finally {
385
+ cleanupTestProject(tmpDir);
386
+ }
387
+ });
388
+ }
389
+
390
+ // ============================================================================
391
+ // Create Command Tests
392
+ // ============================================================================
393
+
394
+ function testCreateCommand() {
395
+ console.log(`\n${colors.blue}${colors.bold}Create Command Tests${colors.reset}`);
396
+
397
+ test('scope create with all options', () => {
398
+ const tmpDir = createTestProject();
399
+ try {
400
+ runCli('scope init', tmpDir);
401
+ const result = runCli('scope create auth --name "Authentication" --description "User auth"', tmpDir);
402
+ assertTrue(result.success, `Create should succeed: ${result.stderr || result.error}`);
403
+ assertContains(result.output, "Scope 'auth' created successfully");
404
+
405
+ // Check directories created
406
+ assertExists(path.join(tmpDir, '_bmad-output', 'auth', 'planning-artifacts'));
407
+ assertExists(path.join(tmpDir, '_bmad-output', 'auth', 'implementation-artifacts'));
408
+ assertExists(path.join(tmpDir, '_bmad-output', 'auth', 'tests'));
409
+ } finally {
410
+ cleanupTestProject(tmpDir);
411
+ }
412
+ });
413
+
414
+ test('scope create with dependencies', () => {
415
+ const tmpDir = createTestProject();
416
+ try {
417
+ runCli('scope init', tmpDir);
418
+ runCli('scope create users --name "Users" --description ""', tmpDir);
419
+ const result = runCli('scope create auth --name "Auth" --description "" --deps users', tmpDir);
420
+ assertTrue(result.success, 'Create with deps should succeed');
421
+ } finally {
422
+ cleanupTestProject(tmpDir);
423
+ }
424
+ });
425
+
426
+ test('scope create with --context flag', () => {
427
+ const tmpDir = createTestProject();
428
+ try {
429
+ runCli('scope init', tmpDir);
430
+ const result = runCli('scope create auth --name "Auth" --description "" --context', tmpDir);
431
+ assertTrue(result.success, 'Create with context should succeed');
432
+ // Note: project-context.md creation depends on ScopeInitializer implementation
433
+ } finally {
434
+ cleanupTestProject(tmpDir);
435
+ }
436
+ });
437
+
438
+ test('scope create auto-initializes if needed', () => {
439
+ const tmpDir = createTestProject();
440
+ try {
441
+ // Don't run init, but create should auto-init
442
+ const result = runCli('scope create auth --name "Auth" --description ""', tmpDir);
443
+ assertTrue(result.success, 'Create should auto-init');
444
+ assertExists(path.join(tmpDir, '_bmad', '_config', 'scopes.yaml'));
445
+ } finally {
446
+ cleanupTestProject(tmpDir);
447
+ }
448
+ });
449
+
450
+ test('scope create rejects invalid ID (uppercase)', () => {
451
+ const tmpDir = createTestProject();
452
+ try {
453
+ runCli('scope init', tmpDir);
454
+ const result = runCli('scope create Auth --name "Auth"', tmpDir);
455
+ assertFalse(result.success, 'Should reject uppercase');
456
+ assertContains(result.output + result.stderr, 'Error');
457
+ } finally {
458
+ cleanupTestProject(tmpDir);
459
+ }
460
+ });
461
+
462
+ test('scope create rejects invalid ID (underscore)', () => {
463
+ const tmpDir = createTestProject();
464
+ try {
465
+ runCli('scope init', tmpDir);
466
+ const result = runCli('scope create user_auth --name "Auth" --description ""', tmpDir);
467
+ assertFalse(result.success, 'Should reject underscore');
468
+ } finally {
469
+ cleanupTestProject(tmpDir);
470
+ }
471
+ });
472
+
473
+ test('scope create rejects reserved name _shared', () => {
474
+ const tmpDir = createTestProject();
475
+ try {
476
+ runCli('scope init', tmpDir);
477
+ const result = runCli('scope create _shared --name "Shared" --description ""', tmpDir);
478
+ assertFalse(result.success, 'Should reject _shared');
479
+ } finally {
480
+ cleanupTestProject(tmpDir);
481
+ }
482
+ });
483
+
484
+ test('scope new is alias for create', () => {
485
+ const tmpDir = createTestProject();
486
+ try {
487
+ runCli('scope init', tmpDir);
488
+ const result = runCli('scope new auth --name "Auth"', tmpDir);
489
+ assertTrue(result.success, 'new alias should work');
490
+ } finally {
491
+ cleanupTestProject(tmpDir);
492
+ }
493
+ });
494
+ }
495
+
496
+ // ============================================================================
497
+ // List Command Tests
498
+ // ============================================================================
499
+
500
+ function testListCommand() {
501
+ console.log(`\n${colors.blue}${colors.bold}List Command Tests${colors.reset}`);
502
+
503
+ test('scope list shows no scopes initially', () => {
504
+ const tmpDir = createTestProject();
505
+ try {
506
+ runCli('scope init', tmpDir);
507
+ const result = runCli('scope list', tmpDir);
508
+ assertContains(result.output, 'No scopes found');
509
+ } finally {
510
+ cleanupTestProject(tmpDir);
511
+ }
512
+ });
513
+
514
+ test('scope list shows created scopes', () => {
515
+ const tmpDir = createTestProject();
516
+ try {
517
+ runCli('scope init', tmpDir);
518
+ runCli('scope create auth --name "Authentication" --description ""', tmpDir);
519
+ runCli('scope create payments --name "Payments" --description ""', tmpDir);
520
+
521
+ const result = runCli('scope list', tmpDir);
522
+ assertContains(result.output, 'auth');
523
+ assertContains(result.output, 'payments');
524
+ assertContains(result.output, 'Authentication');
525
+ assertContains(result.output, 'Payments');
526
+ } finally {
527
+ cleanupTestProject(tmpDir);
528
+ }
529
+ });
530
+
531
+ test('scope list --status active filters', () => {
532
+ const tmpDir = createTestProject();
533
+ try {
534
+ runCli('scope init', tmpDir);
535
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
536
+ runCli('scope create old --name "Old" --description ""', tmpDir);
537
+ runCli('scope archive old', tmpDir);
538
+
539
+ const result = runCli('scope list --status active', tmpDir);
540
+ assertContains(result.output, 'auth');
541
+ assertNotContains(result.output, 'old');
542
+ } finally {
543
+ cleanupTestProject(tmpDir);
544
+ }
545
+ });
546
+
547
+ test('scope list --status archived filters', () => {
548
+ const tmpDir = createTestProject();
549
+ try {
550
+ runCli('scope init', tmpDir);
551
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
552
+ runCli('scope create old --name "Old" --description ""', tmpDir);
553
+ runCli('scope archive old', tmpDir);
554
+
555
+ const result = runCli('scope list --status archived', tmpDir);
556
+ assertContains(result.output, 'old');
557
+ } finally {
558
+ cleanupTestProject(tmpDir);
559
+ }
560
+ });
561
+
562
+ test('scope ls is alias for list', () => {
563
+ const tmpDir = createTestProject();
564
+ try {
565
+ runCli('scope init', tmpDir);
566
+ const result = runCli('scope ls', tmpDir);
567
+ assertTrue(result.success, 'ls alias should work');
568
+ } finally {
569
+ cleanupTestProject(tmpDir);
570
+ }
571
+ });
572
+
573
+ test('scope list without init shows helpful message', () => {
574
+ const tmpDir = createTestProject();
575
+ try {
576
+ // Remove the _config directory to simulate uninitialized
577
+ fs.rmSync(path.join(tmpDir, '_bmad', '_config'), { recursive: true, force: true });
578
+
579
+ const result = runCli('scope list', tmpDir);
580
+ assertContains(result.output, 'not initialized');
581
+ } finally {
582
+ cleanupTestProject(tmpDir);
583
+ }
584
+ });
585
+ }
586
+
587
+ // ============================================================================
588
+ // Info Command Tests
589
+ // ============================================================================
590
+
591
+ function testInfoCommand() {
592
+ console.log(`\n${colors.blue}${colors.bold}Info Command Tests${colors.reset}`);
593
+
594
+ test('scope info shows scope details', () => {
595
+ const tmpDir = createTestProject();
596
+ try {
597
+ runCli('scope init', tmpDir);
598
+ runCli('scope create auth --name "Authentication" --description "User auth system"', tmpDir);
599
+
600
+ const result = runCli('scope info auth', tmpDir);
601
+ assertTrue(result.success, 'Info should succeed');
602
+ assertContains(result.output, 'auth');
603
+ assertContains(result.output, 'Authentication');
604
+ assertContains(result.output, 'active');
605
+ assertContains(result.output, 'planning-artifacts');
606
+ } finally {
607
+ cleanupTestProject(tmpDir);
608
+ }
609
+ });
610
+
611
+ test('scope info shows dependencies', () => {
612
+ const tmpDir = createTestProject();
613
+ try {
614
+ runCli('scope init', tmpDir);
615
+ runCli('scope create users --name "Users" --description ""', tmpDir);
616
+ runCli('scope create auth --name "Auth" --description "" --deps users', tmpDir);
617
+
618
+ const result = runCli('scope info auth', tmpDir);
619
+ assertContains(result.output, 'Dependencies');
620
+ assertContains(result.output, 'users');
621
+ } finally {
622
+ cleanupTestProject(tmpDir);
623
+ }
624
+ });
625
+
626
+ test('scope info on non-existent scope fails', () => {
627
+ const tmpDir = createTestProject();
628
+ try {
629
+ runCli('scope init', tmpDir);
630
+ const result = runCli('scope info nonexistent', tmpDir);
631
+ assertFalse(result.success, 'Should fail for non-existent scope');
632
+ assertContains(result.output + result.stderr, 'not found');
633
+ } finally {
634
+ cleanupTestProject(tmpDir);
635
+ }
636
+ });
637
+
638
+ test('scope info requires ID', () => {
639
+ const tmpDir = createTestProject();
640
+ try {
641
+ runCli('scope init', tmpDir);
642
+ const result = runCli('scope info', tmpDir);
643
+ assertFalse(result.success, 'Should require ID');
644
+ assertContains(result.output + result.stderr, 'required');
645
+ } finally {
646
+ cleanupTestProject(tmpDir);
647
+ }
648
+ });
649
+
650
+ test('scope show is alias for info', () => {
651
+ const tmpDir = createTestProject();
652
+ try {
653
+ runCli('scope init', tmpDir);
654
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
655
+ const result = runCli('scope show auth', tmpDir);
656
+ assertTrue(result.success, 'show alias should work');
657
+ } finally {
658
+ cleanupTestProject(tmpDir);
659
+ }
660
+ });
661
+
662
+ test('scope <id> shorthand shows info', () => {
663
+ const tmpDir = createTestProject();
664
+ try {
665
+ runCli('scope init', tmpDir);
666
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
667
+ const result = runCli('scope auth', tmpDir);
668
+ assertTrue(result.success, 'shorthand should work');
669
+ assertContains(result.output, 'auth');
670
+ } finally {
671
+ cleanupTestProject(tmpDir);
672
+ }
673
+ });
674
+ }
675
+
676
+ // ============================================================================
677
+ // Set/Unset Command Tests
678
+ // ============================================================================
679
+
680
+ function testSetUnsetCommands() {
681
+ console.log(`\n${colors.blue}${colors.bold}Set/Unset Command Tests${colors.reset}`);
682
+
683
+ test('scope set creates .bmad-scope file', () => {
684
+ const tmpDir = createTestProject();
685
+ try {
686
+ runCli('scope init', tmpDir);
687
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
688
+
689
+ const result = runCli('scope set auth', tmpDir);
690
+ assertTrue(result.success, `Set should succeed: ${result.stderr || result.error}`);
691
+ assertContains(result.output, "Active scope set to 'auth'");
692
+
693
+ // Check file created
694
+ const scopeFile = path.join(tmpDir, '.bmad-scope');
695
+ assertExists(scopeFile);
696
+
697
+ const content = fs.readFileSync(scopeFile, 'utf8');
698
+ assertContains(content, 'active_scope: auth');
699
+ } finally {
700
+ cleanupTestProject(tmpDir);
701
+ }
702
+ });
703
+
704
+ test('scope set validates scope exists', () => {
705
+ const tmpDir = createTestProject();
706
+ try {
707
+ runCli('scope init', tmpDir);
708
+ const result = runCli('scope set nonexistent', tmpDir);
709
+ assertFalse(result.success, 'Should fail for non-existent scope');
710
+ assertContains(result.output + result.stderr, 'not found');
711
+ } finally {
712
+ cleanupTestProject(tmpDir);
713
+ }
714
+ });
715
+
716
+ test('scope set warns for archived scope', () => {
717
+ const tmpDir = createTestProject();
718
+ try {
719
+ runCli('scope init', tmpDir);
720
+ runCli('scope create old --name "Old" --description ""', tmpDir);
721
+ runCli('scope archive old', tmpDir);
722
+
723
+ // This will prompt for confirmation - we can't easily test interactive mode
724
+ // Just verify it doesn't crash with the scope being archived
725
+ const result = runCli('scope info old', tmpDir);
726
+ assertContains(result.output, 'archived');
727
+ } finally {
728
+ cleanupTestProject(tmpDir);
729
+ }
730
+ });
731
+
732
+ test('scope unset removes .bmad-scope file', () => {
733
+ const tmpDir = createTestProject();
734
+ try {
735
+ runCli('scope init', tmpDir);
736
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
737
+ runCli('scope set auth', tmpDir);
738
+
739
+ const scopeFile = path.join(tmpDir, '.bmad-scope');
740
+ assertExists(scopeFile);
741
+
742
+ const result = runCli('scope unset', tmpDir);
743
+ assertTrue(result.success, 'Unset should succeed');
744
+ assertContains(result.output, 'Active scope cleared');
745
+ assertNotExists(scopeFile);
746
+ } finally {
747
+ cleanupTestProject(tmpDir);
748
+ }
749
+ });
750
+
751
+ test('scope unset when no scope is set', () => {
752
+ const tmpDir = createTestProject();
753
+ try {
754
+ runCli('scope init', tmpDir);
755
+ const result = runCli('scope unset', tmpDir);
756
+ assertTrue(result.success, 'Unset should succeed even if no scope');
757
+ assertContains(result.output, 'No active scope');
758
+ } finally {
759
+ cleanupTestProject(tmpDir);
760
+ }
761
+ });
762
+
763
+ test('scope use is alias for set', () => {
764
+ const tmpDir = createTestProject();
765
+ try {
766
+ runCli('scope init', tmpDir);
767
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
768
+ const result = runCli('scope use auth', tmpDir);
769
+ assertTrue(result.success, 'use alias should work');
770
+ } finally {
771
+ cleanupTestProject(tmpDir);
772
+ }
773
+ });
774
+
775
+ test('scope clear is alias for unset', () => {
776
+ const tmpDir = createTestProject();
777
+ try {
778
+ runCli('scope init', tmpDir);
779
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
780
+ runCli('scope set auth', tmpDir);
781
+ const result = runCli('scope clear', tmpDir);
782
+ assertTrue(result.success, 'clear alias should work');
783
+ } finally {
784
+ cleanupTestProject(tmpDir);
785
+ }
786
+ });
787
+ }
788
+
789
+ // ============================================================================
790
+ // Archive/Activate Command Tests
791
+ // ============================================================================
792
+
793
+ function testArchiveActivateCommands() {
794
+ console.log(`\n${colors.blue}${colors.bold}Archive/Activate Command Tests${colors.reset}`);
795
+
796
+ test('scope archive changes status', () => {
797
+ const tmpDir = createTestProject();
798
+ try {
799
+ runCli('scope init', tmpDir);
800
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
801
+
802
+ const result = runCli('scope archive auth', tmpDir);
803
+ assertTrue(result.success, 'Archive should succeed');
804
+ assertContains(result.output, 'archived');
805
+
806
+ // Verify status changed
807
+ const infoResult = runCli('scope info auth', tmpDir);
808
+ assertContains(infoResult.output, 'archived');
809
+ } finally {
810
+ cleanupTestProject(tmpDir);
811
+ }
812
+ });
813
+
814
+ test('scope archive requires ID', () => {
815
+ const tmpDir = createTestProject();
816
+ try {
817
+ runCli('scope init', tmpDir);
818
+ const result = runCli('scope archive', tmpDir);
819
+ assertFalse(result.success, 'Should require ID');
820
+ } finally {
821
+ cleanupTestProject(tmpDir);
822
+ }
823
+ });
824
+
825
+ test('scope activate reactivates archived scope', () => {
826
+ const tmpDir = createTestProject();
827
+ try {
828
+ runCli('scope init', tmpDir);
829
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
830
+ runCli('scope archive auth', tmpDir);
831
+
832
+ const result = runCli('scope activate auth', tmpDir);
833
+ assertTrue(result.success, 'Activate should succeed');
834
+ assertContains(result.output, 'activated');
835
+
836
+ // Verify status changed back
837
+ const infoResult = runCli('scope info auth', tmpDir);
838
+ assertContains(infoResult.output, 'active');
839
+ } finally {
840
+ cleanupTestProject(tmpDir);
841
+ }
842
+ });
843
+
844
+ test('scope activate requires ID', () => {
845
+ const tmpDir = createTestProject();
846
+ try {
847
+ runCli('scope init', tmpDir);
848
+ const result = runCli('scope activate', tmpDir);
849
+ assertFalse(result.success, 'Should require ID');
850
+ } finally {
851
+ cleanupTestProject(tmpDir);
852
+ }
853
+ });
854
+ }
855
+
856
+ // ============================================================================
857
+ // Remove Command Tests
858
+ // ============================================================================
859
+
860
+ function testRemoveCommand() {
861
+ console.log(`\n${colors.blue}${colors.bold}Remove Command Tests${colors.reset}`);
862
+
863
+ test('scope remove --force removes scope', () => {
864
+ const tmpDir = createTestProject();
865
+ try {
866
+ runCli('scope init', tmpDir);
867
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
868
+
869
+ const result = runCli('scope remove auth --force', tmpDir);
870
+ assertTrue(result.success, 'Remove should succeed');
871
+ assertContains(result.output, 'removed successfully');
872
+
873
+ // Verify scope is gone
874
+ const listResult = runCli('scope list', tmpDir);
875
+ assertNotContains(listResult.output, 'auth');
876
+ } finally {
877
+ cleanupTestProject(tmpDir);
878
+ }
879
+ });
880
+
881
+ test('scope remove creates backup by default', () => {
882
+ const tmpDir = createTestProject();
883
+ try {
884
+ runCli('scope init', tmpDir);
885
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
886
+
887
+ const result = runCli('scope remove auth --force', tmpDir);
888
+ assertContains(result.output, 'backup');
889
+ } finally {
890
+ cleanupTestProject(tmpDir);
891
+ }
892
+ });
893
+
894
+ test('scope remove --force --no-backup skips backup', () => {
895
+ const tmpDir = createTestProject();
896
+ try {
897
+ runCli('scope init', tmpDir);
898
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
899
+
900
+ const result = runCli('scope remove auth --force --no-backup', tmpDir);
901
+ assertTrue(result.success, 'Remove should succeed');
902
+ assertNotContains(result.output, 'backup was created');
903
+ } finally {
904
+ cleanupTestProject(tmpDir);
905
+ }
906
+ });
907
+
908
+ test('scope remove requires ID', () => {
909
+ const tmpDir = createTestProject();
910
+ try {
911
+ runCli('scope init', tmpDir);
912
+ const result = runCli('scope remove --force', tmpDir);
913
+ assertFalse(result.success, 'Should require ID');
914
+ } finally {
915
+ cleanupTestProject(tmpDir);
916
+ }
917
+ });
918
+
919
+ test('scope remove on non-existent scope fails', () => {
920
+ const tmpDir = createTestProject();
921
+ try {
922
+ runCli('scope init', tmpDir);
923
+ const result = runCli('scope remove nonexistent --force', tmpDir);
924
+ assertFalse(result.success, 'Should fail');
925
+ assertContains(result.output + result.stderr, 'not found');
926
+ } finally {
927
+ cleanupTestProject(tmpDir);
928
+ }
929
+ });
930
+
931
+ test('scope rm is alias for remove', () => {
932
+ const tmpDir = createTestProject();
933
+ try {
934
+ runCli('scope init', tmpDir);
935
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
936
+ const result = runCli('scope rm auth --force', tmpDir);
937
+ assertTrue(result.success, 'rm alias should work');
938
+ } finally {
939
+ cleanupTestProject(tmpDir);
940
+ }
941
+ });
942
+
943
+ test('scope delete is alias for remove', () => {
944
+ const tmpDir = createTestProject();
945
+ try {
946
+ runCli('scope init', tmpDir);
947
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
948
+ const result = runCli('scope delete auth --force', tmpDir);
949
+ assertTrue(result.success, 'delete alias should work');
950
+ } finally {
951
+ cleanupTestProject(tmpDir);
952
+ }
953
+ });
954
+ }
955
+
956
+ // ============================================================================
957
+ // Sync Command Tests
958
+ // ============================================================================
959
+
960
+ function testSyncCommands() {
961
+ console.log(`\n${colors.blue}${colors.bold}Sync Command Tests${colors.reset}`);
962
+
963
+ test('scope sync-up requires scope ID', () => {
964
+ const tmpDir = createTestProject();
965
+ try {
966
+ runCli('scope init', tmpDir);
967
+ const result = runCli('scope sync-up', tmpDir);
968
+ assertFalse(result.success, 'Should require ID');
969
+ assertContains(result.output + result.stderr, 'required');
970
+ } finally {
971
+ cleanupTestProject(tmpDir);
972
+ }
973
+ });
974
+
975
+ test('scope sync-up validates scope exists', () => {
976
+ const tmpDir = createTestProject();
977
+ try {
978
+ runCli('scope init', tmpDir);
979
+ const result = runCli('scope sync-up nonexistent', tmpDir);
980
+ assertFalse(result.success, 'Should fail for non-existent scope');
981
+ } finally {
982
+ cleanupTestProject(tmpDir);
983
+ }
984
+ });
985
+
986
+ test('scope sync-up --dry-run shows analysis', () => {
987
+ const tmpDir = createTestProject();
988
+ try {
989
+ runCli('scope init', tmpDir);
990
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
991
+
992
+ const result = runCli('scope sync-up auth --dry-run', tmpDir);
993
+ assertTrue(result.success, 'Dry run should succeed');
994
+ assertContains(result.output, 'Dry Run');
995
+ assertContains(result.output, 'patterns');
996
+ } finally {
997
+ cleanupTestProject(tmpDir);
998
+ }
999
+ });
1000
+
1001
+ test('scope sync-up runs without errors', () => {
1002
+ const tmpDir = createTestProject();
1003
+ try {
1004
+ runCli('scope init', tmpDir);
1005
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1006
+
1007
+ const result = runCli('scope sync-up auth', tmpDir);
1008
+ assertTrue(result.success, `Sync-up should succeed: ${result.stderr || result.error}`);
1009
+ } finally {
1010
+ cleanupTestProject(tmpDir);
1011
+ }
1012
+ });
1013
+
1014
+ test('scope sync-down requires scope ID', () => {
1015
+ const tmpDir = createTestProject();
1016
+ try {
1017
+ runCli('scope init', tmpDir);
1018
+ const result = runCli('scope sync-down', tmpDir);
1019
+ assertFalse(result.success, 'Should require ID');
1020
+ } finally {
1021
+ cleanupTestProject(tmpDir);
1022
+ }
1023
+ });
1024
+
1025
+ test('scope sync-down validates scope exists', () => {
1026
+ const tmpDir = createTestProject();
1027
+ try {
1028
+ runCli('scope init', tmpDir);
1029
+ const result = runCli('scope sync-down nonexistent', tmpDir);
1030
+ assertFalse(result.success, 'Should fail for non-existent scope');
1031
+ } finally {
1032
+ cleanupTestProject(tmpDir);
1033
+ }
1034
+ });
1035
+
1036
+ test('scope sync-down --dry-run shows analysis', () => {
1037
+ const tmpDir = createTestProject();
1038
+ try {
1039
+ runCli('scope init', tmpDir);
1040
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1041
+
1042
+ const result = runCli('scope sync-down auth --dry-run', tmpDir);
1043
+ assertTrue(result.success, 'Dry run should succeed');
1044
+ assertContains(result.output, 'Dry Run');
1045
+ } finally {
1046
+ cleanupTestProject(tmpDir);
1047
+ }
1048
+ });
1049
+
1050
+ test('scope sync-down runs without errors', () => {
1051
+ const tmpDir = createTestProject();
1052
+ try {
1053
+ runCli('scope init', tmpDir);
1054
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1055
+
1056
+ const result = runCli('scope sync-down auth', tmpDir);
1057
+ assertTrue(result.success, `Sync-down should succeed: ${result.stderr || result.error}`);
1058
+ } finally {
1059
+ cleanupTestProject(tmpDir);
1060
+ }
1061
+ });
1062
+
1063
+ test('scope syncup is alias for sync-up', () => {
1064
+ const tmpDir = createTestProject();
1065
+ try {
1066
+ runCli('scope init', tmpDir);
1067
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1068
+ const result = runCli('scope syncup auth --dry-run', tmpDir);
1069
+ assertTrue(result.success, 'syncup alias should work');
1070
+ } finally {
1071
+ cleanupTestProject(tmpDir);
1072
+ }
1073
+ });
1074
+
1075
+ test('scope syncdown is alias for sync-down', () => {
1076
+ const tmpDir = createTestProject();
1077
+ try {
1078
+ runCli('scope init', tmpDir);
1079
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1080
+ const result = runCli('scope syncdown auth --dry-run', tmpDir);
1081
+ assertTrue(result.success, 'syncdown alias should work');
1082
+ } finally {
1083
+ cleanupTestProject(tmpDir);
1084
+ }
1085
+ });
1086
+ }
1087
+
1088
+ // ============================================================================
1089
+ // Edge Cases and Error Handling Tests
1090
+ // ============================================================================
1091
+
1092
+ function testEdgeCases() {
1093
+ console.log(`\n${colors.blue}${colors.bold}Edge Cases and Error Handling Tests${colors.reset}`);
1094
+
1095
+ test('handles special characters in scope name', () => {
1096
+ const tmpDir = createTestProject();
1097
+ try {
1098
+ runCli('scope init', tmpDir);
1099
+ const result = runCli('scope create auth --name "Auth & Users (v2)" --description ""', tmpDir);
1100
+ assertTrue(result.success, 'Should handle special chars in name');
1101
+ } finally {
1102
+ cleanupTestProject(tmpDir);
1103
+ }
1104
+ });
1105
+
1106
+ test('handles empty description', () => {
1107
+ const tmpDir = createTestProject();
1108
+ try {
1109
+ runCli('scope init', tmpDir);
1110
+ const result = runCli('scope create auth --name "Auth" --description "" --description ""', tmpDir);
1111
+ assertTrue(result.success, 'Should handle empty description');
1112
+ } finally {
1113
+ cleanupTestProject(tmpDir);
1114
+ }
1115
+ });
1116
+
1117
+ test('handles multiple dependencies', () => {
1118
+ const tmpDir = createTestProject();
1119
+ try {
1120
+ runCli('scope init', tmpDir);
1121
+ runCli('scope create users --name "Users" --description ""', tmpDir);
1122
+ runCli('scope create notifications --name "Notifications" --description ""', tmpDir);
1123
+ runCli('scope create logging --name "Logging" --description ""', tmpDir);
1124
+
1125
+ const result = runCli('scope create auth --name "Auth" --description "" --deps users,notifications,logging', tmpDir);
1126
+ assertTrue(result.success, 'Should handle multiple deps');
1127
+ } finally {
1128
+ cleanupTestProject(tmpDir);
1129
+ }
1130
+ });
1131
+
1132
+ test('handles long scope ID', () => {
1133
+ const tmpDir = createTestProject();
1134
+ try {
1135
+ runCli('scope init', tmpDir);
1136
+ const longId = 'a'.repeat(50);
1137
+ const result = runCli(`scope create ${longId} --name "Long ID"`, tmpDir);
1138
+ assertTrue(result.success, 'Should handle long ID');
1139
+ } finally {
1140
+ cleanupTestProject(tmpDir);
1141
+ }
1142
+ });
1143
+
1144
+ test('rejects too long scope ID', () => {
1145
+ const tmpDir = createTestProject();
1146
+ try {
1147
+ runCli('scope init', tmpDir);
1148
+ const tooLongId = 'a'.repeat(51);
1149
+ const result = runCli(`scope create ${tooLongId} --name "Too Long"`, tmpDir);
1150
+ assertFalse(result.success, 'Should reject too long ID');
1151
+ } finally {
1152
+ cleanupTestProject(tmpDir);
1153
+ }
1154
+ });
1155
+
1156
+ test('DEBUG env var enables verbose output', () => {
1157
+ const tmpDir = createTestProject();
1158
+ try {
1159
+ runCli('scope init', tmpDir);
1160
+ runCli('scope create auth --name "Auth" --description ""', tmpDir);
1161
+
1162
+ // Trigger an error with DEBUG enabled
1163
+ const result = runCli('scope info nonexistent', tmpDir, { env: { DEBUG: 'true' } });
1164
+ // Just verify it doesn't crash with DEBUG enabled
1165
+ assertFalse(result.success);
1166
+ } finally {
1167
+ cleanupTestProject(tmpDir);
1168
+ }
1169
+ });
1170
+ }
1171
+
1172
+ // ============================================================================
1173
+ // Integration Tests
1174
+ // ============================================================================
1175
+
1176
+ function testIntegration() {
1177
+ console.log(`\n${colors.blue}${colors.bold}Integration Tests${colors.reset}`);
1178
+
1179
+ test('full workflow: init -> create -> set -> list -> archive -> activate -> remove', () => {
1180
+ const tmpDir = createTestProject();
1181
+ try {
1182
+ // Init
1183
+ let result = runCli('scope init', tmpDir);
1184
+ assertTrue(result.success, 'Init failed');
1185
+
1186
+ // Create scopes
1187
+ result = runCli('scope create auth --name "Authentication" --description ""', tmpDir);
1188
+ assertTrue(result.success, 'Create auth failed');
1189
+
1190
+ result = runCli('scope create payments --name "Payments" --description "" --deps auth', tmpDir);
1191
+ assertTrue(result.success, 'Create payments failed');
1192
+
1193
+ // Set active scope
1194
+ result = runCli('scope set auth', tmpDir);
1195
+ assertTrue(result.success, 'Set failed');
1196
+
1197
+ // List scopes
1198
+ result = runCli('scope list', tmpDir);
1199
+ assertTrue(result.success, 'List failed');
1200
+ assertContains(result.output, 'auth');
1201
+ assertContains(result.output, 'payments');
1202
+
1203
+ // Archive
1204
+ result = runCli('scope archive auth', tmpDir);
1205
+ assertTrue(result.success, 'Archive failed');
1206
+
1207
+ // Activate
1208
+ result = runCli('scope activate auth', tmpDir);
1209
+ assertTrue(result.success, 'Activate failed');
1210
+
1211
+ // Unset
1212
+ result = runCli('scope unset', tmpDir);
1213
+ assertTrue(result.success, 'Unset failed');
1214
+
1215
+ // Remove
1216
+ result = runCli('scope remove payments --force', tmpDir);
1217
+ assertTrue(result.success, 'Remove payments failed');
1218
+
1219
+ result = runCli('scope remove auth --force', tmpDir);
1220
+ assertTrue(result.success, 'Remove auth failed');
1221
+
1222
+ // Verify all gone
1223
+ result = runCli('scope list', tmpDir);
1224
+ assertContains(result.output, 'No scopes found');
1225
+ } finally {
1226
+ cleanupTestProject(tmpDir);
1227
+ }
1228
+ });
1229
+
1230
+ test('parallel scopes simulation', () => {
1231
+ const tmpDir = createTestProject();
1232
+ try {
1233
+ runCli('scope init', tmpDir);
1234
+
1235
+ // Create multiple scopes (simulating parallel development)
1236
+ runCli('scope create frontend --name "Frontend" --description ""', tmpDir);
1237
+ runCli('scope create backend --name "Backend" --description ""', tmpDir);
1238
+ runCli('scope create mobile --name "Mobile" --description "" --deps backend', tmpDir);
1239
+
1240
+ // Verify all exist
1241
+ const result = runCli('scope list', tmpDir);
1242
+ assertContains(result.output, 'frontend');
1243
+ assertContains(result.output, 'backend');
1244
+ assertContains(result.output, 'mobile');
1245
+
1246
+ // Check dependencies
1247
+ const infoResult = runCli('scope info mobile', tmpDir);
1248
+ assertContains(infoResult.output, 'backend');
1249
+ } finally {
1250
+ cleanupTestProject(tmpDir);
1251
+ }
1252
+ });
1253
+ }
1254
+
1255
+ // ============================================================================
1256
+ // Main Test Runner
1257
+ // ============================================================================
1258
+
1259
+ function main() {
1260
+ console.log(`\n${colors.bold}BMAD Scope CLI Test Suite${colors.reset}`);
1261
+ console.log(colors.dim + '═'.repeat(70) + colors.reset);
1262
+
1263
+ const startTime = Date.now();
1264
+
1265
+ // Run all test suites
1266
+ testHelpSystem();
1267
+ testInitCommand();
1268
+ testCreateCommand();
1269
+ testListCommand();
1270
+ testInfoCommand();
1271
+ testSetUnsetCommands();
1272
+ testArchiveActivateCommands();
1273
+ testRemoveCommand();
1274
+ testSyncCommands();
1275
+ testEdgeCases();
1276
+ testIntegration();
1277
+
1278
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
1279
+
1280
+ // Summary
1281
+ console.log(`\n${colors.dim}${'─'.repeat(70)}${colors.reset}`);
1282
+ console.log(`\n${colors.bold}Test Results${colors.reset}`);
1283
+ console.log(` Total: ${testCount}`);
1284
+ console.log(` ${colors.green}Passed: ${passCount}${colors.reset}`);
1285
+ if (failCount > 0) {
1286
+ console.log(` ${colors.red}Failed: ${failCount}${colors.reset}`);
1287
+ }
1288
+ if (skipCount > 0) {
1289
+ console.log(` ${colors.yellow}Skipped: ${skipCount}${colors.reset}`);
1290
+ }
1291
+ console.log(` Time: ${duration}s`);
1292
+
1293
+ if (failures.length > 0) {
1294
+ console.log(`\n${colors.red}${colors.bold}Failures:${colors.reset}`);
1295
+ for (const { name, error } of failures) {
1296
+ console.log(`\n ${colors.red}✗${colors.reset} ${name}`);
1297
+ console.log(` ${colors.dim}${error}${colors.reset}`);
1298
+ }
1299
+ process.exit(1);
1300
+ }
1301
+
1302
+ console.log(`\n${colors.green}${colors.bold}All tests passed!${colors.reset}\n`);
1303
+ process.exit(0);
1304
+ }
1305
+
1306
+ main();