bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.24

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.
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Scope System Test Suite
3
- *
3
+ *
4
4
  * Tests for multi-scope parallel artifacts system including:
5
5
  * - ScopeValidator: ID validation, schema validation, circular dependency detection
6
6
  * - ScopeManager: CRUD operations, path resolution
7
7
  * - ArtifactResolver: Read/write access control
8
8
  * - StateLock: File locking and optimistic versioning
9
- *
9
+ *
10
10
  * Usage: node test/test-scope-system.js
11
11
  * Exit codes: 0 = all tests pass, 1 = test failures
12
12
  */
@@ -104,7 +104,7 @@ function cleanupTempDir(tmpDir) {
104
104
 
105
105
  function testScopeValidator() {
106
106
  console.log(`\n${colors.blue}ScopeValidator Tests${colors.reset}`);
107
-
107
+
108
108
  const { ScopeValidator } = require('../src/core/lib/scope/scope-validator');
109
109
  const validator = new ScopeValidator();
110
110
 
@@ -209,11 +209,11 @@ function testScopeValidator() {
209
209
 
210
210
  function testScopeManager() {
211
211
  console.log(`\n${colors.blue}ScopeManager Tests${colors.reset}`);
212
-
212
+
213
213
  const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
214
-
214
+
215
215
  let tmpDir;
216
-
216
+
217
217
  // Setup/teardown for each test
218
218
  function setup() {
219
219
  tmpDir = createTempDir();
@@ -222,7 +222,7 @@ function testScopeManager() {
222
222
  fs.mkdirSync(path.join(tmpDir, '_bmad-output'), { recursive: true });
223
223
  return new ScopeManager({ projectRoot: tmpDir });
224
224
  }
225
-
225
+
226
226
  function teardown() {
227
227
  if (tmpDir) {
228
228
  cleanupTempDir(tmpDir);
@@ -260,7 +260,7 @@ function testScopeManager() {
260
260
  try {
261
261
  await manager.initialize();
262
262
  await manager.createScope('auth', { name: 'Auth' });
263
-
263
+
264
264
  const scopePath = path.join(tmpDir, '_bmad-output', 'auth');
265
265
  assertTrue(fs.existsSync(scopePath), 'Scope directory should exist');
266
266
  assertTrue(fs.existsSync(path.join(scopePath, 'planning-artifacts')), 'planning-artifacts should exist');
@@ -310,7 +310,7 @@ function testScopeManager() {
310
310
  try {
311
311
  await manager.initialize();
312
312
  await manager.createScope('auth', { name: 'Authentication', description: 'Auth service' });
313
-
313
+
314
314
  const scope = await manager.getScope('auth');
315
315
  assertEqual(scope.id, 'auth', 'ID should match');
316
316
  assertEqual(scope.name, 'Authentication', 'Name should match');
@@ -338,7 +338,7 @@ function testScopeManager() {
338
338
  await manager.initialize();
339
339
  await manager.createScope('auth', { name: 'Auth' });
340
340
  await manager.createScope('payments', { name: 'Payments' });
341
-
341
+
342
342
  const scopes = await manager.listScopes();
343
343
  assertEqual(scopes.length, 2, 'Should have 2 scopes');
344
344
  } finally {
@@ -353,7 +353,7 @@ function testScopeManager() {
353
353
  await manager.createScope('auth', { name: 'Auth' });
354
354
  await manager.createScope('legacy', { name: 'Legacy' });
355
355
  await manager.archiveScope('legacy');
356
-
356
+
357
357
  const activeScopes = await manager.listScopes({ status: 'active' });
358
358
  assertEqual(activeScopes.length, 1, 'Should have 1 active scope');
359
359
  assertEqual(activeScopes[0].id, 'auth', 'Active scope should be auth');
@@ -368,9 +368,9 @@ function testScopeManager() {
368
368
  try {
369
369
  await manager.initialize();
370
370
  await manager.createScope('auth', { name: 'Auth', description: 'Old desc' });
371
-
371
+
372
372
  await manager.updateScope('auth', { description: 'New description' });
373
-
373
+
374
374
  const scope = await manager.getScope('auth');
375
375
  assertEqual(scope.description, 'New description', 'Description should be updated');
376
376
  } finally {
@@ -384,9 +384,9 @@ function testScopeManager() {
384
384
  try {
385
385
  await manager.initialize();
386
386
  await manager.createScope('auth', { name: 'Auth' });
387
-
387
+
388
388
  await manager.archiveScope('auth');
389
-
389
+
390
390
  const scope = await manager.getScope('auth');
391
391
  assertEqual(scope.status, 'archived', 'Status should be archived');
392
392
  } finally {
@@ -400,9 +400,9 @@ function testScopeManager() {
400
400
  await manager.initialize();
401
401
  await manager.createScope('auth', { name: 'Auth' });
402
402
  await manager.archiveScope('auth');
403
-
403
+
404
404
  await manager.activateScope('auth');
405
-
405
+
406
406
  const scope = await manager.getScope('auth');
407
407
  assertEqual(scope.status, 'active', 'Status should be active');
408
408
  } finally {
@@ -416,7 +416,7 @@ function testScopeManager() {
416
416
  try {
417
417
  await manager.initialize();
418
418
  await manager.createScope('auth', { name: 'Auth' });
419
-
419
+
420
420
  const paths = await manager.getScopePaths('auth');
421
421
  assertTrue(paths.root.includes('auth'), 'Root path should contain scope ID');
422
422
  assertTrue(paths.planning.includes('planning-artifacts'), 'Should have planning path');
@@ -434,7 +434,7 @@ function testScopeManager() {
434
434
  await manager.initialize();
435
435
  await manager.createScope('auth', { name: 'Auth' });
436
436
  await manager.createScope('payments', { name: 'Payments', dependencies: ['auth'] });
437
-
437
+
438
438
  const scope = await manager.getScope('payments');
439
439
  assertArrayEqual(scope.dependencies, ['auth'], 'Dependencies should be set');
440
440
  } finally {
@@ -449,7 +449,7 @@ function testScopeManager() {
449
449
  await manager.createScope('auth', { name: 'Auth' });
450
450
  await manager.createScope('payments', { name: 'Payments', dependencies: ['auth'] });
451
451
  await manager.createScope('orders', { name: 'Orders', dependencies: ['auth'] });
452
-
452
+
453
453
  const dependents = await manager.findDependentScopes('auth');
454
454
  assertEqual(dependents.length, 2, 'Should have 2 dependents');
455
455
  assertTrue(dependents.includes('payments'), 'payments should depend on auth');
@@ -466,7 +466,7 @@ function testScopeManager() {
466
466
 
467
467
  function testArtifactResolver() {
468
468
  console.log(`\n${colors.blue}ArtifactResolver Tests${colors.reset}`);
469
-
469
+
470
470
  const { ArtifactResolver } = require('../src/core/lib/scope/artifact-resolver');
471
471
 
472
472
  test('allows read from any scope', () => {
@@ -474,7 +474,7 @@ function testArtifactResolver() {
474
474
  currentScope: 'auth',
475
475
  basePath: '_bmad-output',
476
476
  });
477
-
477
+
478
478
  assertTrue(resolver.canRead('_bmad-output/payments/planning-artifacts/prd.md'), 'Should allow cross-scope read');
479
479
  assertTrue(resolver.canRead('_bmad-output/auth/planning-artifacts/prd.md'), 'Should allow own-scope read');
480
480
  assertTrue(resolver.canRead('_bmad-output/_shared/project-context.md'), 'Should allow shared read');
@@ -485,7 +485,7 @@ function testArtifactResolver() {
485
485
  currentScope: 'auth',
486
486
  basePath: '_bmad-output',
487
487
  });
488
-
488
+
489
489
  assertTrue(resolver.canWrite('_bmad-output/auth/planning-artifacts/prd.md'), 'Should allow own-scope write');
490
490
  });
491
491
 
@@ -495,7 +495,7 @@ function testArtifactResolver() {
495
495
  basePath: '_bmad-output',
496
496
  isolationMode: 'strict',
497
497
  });
498
-
498
+
499
499
  assertFalse(resolver.canWrite('_bmad-output/payments/planning-artifacts/prd.md'), 'Should block cross-scope write');
500
500
  });
501
501
 
@@ -504,7 +504,7 @@ function testArtifactResolver() {
504
504
  currentScope: 'auth',
505
505
  basePath: '_bmad-output',
506
506
  });
507
-
507
+
508
508
  assertFalse(resolver.canWrite('_bmad-output/_shared/project-context.md'), 'Should block _shared write');
509
509
  });
510
510
 
@@ -513,7 +513,7 @@ function testArtifactResolver() {
513
513
  currentScope: 'auth',
514
514
  basePath: '_bmad-output',
515
515
  });
516
-
516
+
517
517
  assertEqual(resolver.extractScopeFromPath('_bmad-output/payments/planning-artifacts/prd.md'), 'payments');
518
518
  assertEqual(resolver.extractScopeFromPath('_bmad-output/auth/tests/unit.js'), 'auth');
519
519
  assertEqual(resolver.extractScopeFromPath('_bmad-output/_shared/context.md'), '_shared');
@@ -525,11 +525,8 @@ function testArtifactResolver() {
525
525
  basePath: '_bmad-output',
526
526
  isolationMode: 'strict',
527
527
  });
528
-
529
- assertThrows(
530
- () => resolver.validateWrite('_bmad-output/payments/prd.md'),
531
- 'Cannot write to scope'
532
- );
528
+
529
+ assertThrows(() => resolver.validateWrite('_bmad-output/payments/prd.md'), 'Cannot write to scope');
533
530
  });
534
531
 
535
532
  test('warns on cross-scope write in warn mode', () => {
@@ -538,7 +535,7 @@ function testArtifactResolver() {
538
535
  basePath: '_bmad-output',
539
536
  isolationMode: 'warn',
540
537
  });
541
-
538
+
542
539
  // Should not throw in warn mode
543
540
  const result = resolver.canWrite('_bmad-output/payments/prd.md');
544
541
  // In warn mode, it may return true but log a warning
@@ -552,16 +549,16 @@ function testArtifactResolver() {
552
549
 
553
550
  function testStateLock() {
554
551
  console.log(`\n${colors.blue}StateLock Tests${colors.reset}`);
555
-
552
+
556
553
  const { StateLock } = require('../src/core/lib/scope/state-lock');
557
-
554
+
558
555
  let tmpDir;
559
-
556
+
560
557
  function setup() {
561
558
  tmpDir = createTempDir();
562
559
  return new StateLock();
563
560
  }
564
-
561
+
565
562
  function teardown() {
566
563
  if (tmpDir) {
567
564
  cleanupTempDir(tmpDir);
@@ -572,11 +569,11 @@ function testStateLock() {
572
569
  const lock = setup();
573
570
  try {
574
571
  const lockPath = path.join(tmpDir, 'test.lock');
575
-
572
+
576
573
  const result = await lock.withLock(lockPath, async () => {
577
574
  return 'success';
578
575
  });
579
-
576
+
580
577
  assertEqual(result, 'success', 'Should return operation result');
581
578
  } finally {
582
579
  teardown();
@@ -588,24 +585,24 @@ function testStateLock() {
588
585
  try {
589
586
  const lockPath = path.join(tmpDir, 'test.lock');
590
587
  const order = [];
591
-
588
+
592
589
  // Start first operation (holds lock)
593
590
  const op1 = lock.withLock(lockPath, async () => {
594
591
  order.push('op1-start');
595
- await new Promise(r => setTimeout(r, 100));
592
+ await new Promise((r) => setTimeout(r, 100));
596
593
  order.push('op1-end');
597
594
  return 'op1';
598
595
  });
599
-
596
+
600
597
  // Start second operation immediately (should wait)
601
- await new Promise(r => setTimeout(r, 10));
598
+ await new Promise((r) => setTimeout(r, 10));
602
599
  const op2 = lock.withLock(lockPath, async () => {
603
600
  order.push('op2');
604
601
  return 'op2';
605
602
  });
606
-
603
+
607
604
  await Promise.all([op1, op2]);
608
-
605
+
609
606
  // op2 should start after op1 ends
610
607
  assertTrue(order.indexOf('op1-end') < order.indexOf('op2'), 'op2 should run after op1 completes');
611
608
  } finally {
@@ -617,13 +614,16 @@ function testStateLock() {
617
614
  const lock = setup();
618
615
  try {
619
616
  const lockPath = path.join(tmpDir, 'test.lock');
620
-
617
+
621
618
  // Create a stale lock file manually
622
- fs.writeFileSync(lockPath, JSON.stringify({
623
- pid: 99_999_999, // Non-existent PID
624
- timestamp: Date.now() - 60_000, // 60 seconds ago
625
- }));
626
-
619
+ fs.writeFileSync(
620
+ lockPath,
621
+ JSON.stringify({
622
+ pid: 99_999_999, // Non-existent PID
623
+ timestamp: Date.now() - 60_000, // 60 seconds ago
624
+ }),
625
+ );
626
+
627
627
  // Should be able to acquire lock despite stale file
628
628
  const result = await lock.withLock(lockPath, async () => 'success');
629
629
  assertEqual(result, 'success', 'Should acquire lock after stale detection');
@@ -639,19 +639,19 @@ function testStateLock() {
639
639
 
640
640
  function testScopeContext() {
641
641
  console.log(`\n${colors.blue}ScopeContext Tests${colors.reset}`);
642
-
642
+
643
643
  const { ScopeContext } = require('../src/core/lib/scope/scope-context');
644
644
  const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
645
-
645
+
646
646
  let tmpDir;
647
-
647
+
648
648
  function setup() {
649
649
  tmpDir = createTempDir();
650
650
  fs.mkdirSync(path.join(tmpDir, '_bmad', '_config'), { recursive: true });
651
651
  fs.mkdirSync(path.join(tmpDir, '_bmad-output', '_shared'), { recursive: true });
652
652
  return new ScopeContext({ projectRoot: tmpDir });
653
653
  }
654
-
654
+
655
655
  function teardown() {
656
656
  if (tmpDir) {
657
657
  cleanupTempDir(tmpDir);
@@ -665,9 +665,9 @@ function testScopeContext() {
665
665
  const manager = new ScopeManager({ projectRoot: tmpDir });
666
666
  await manager.initialize();
667
667
  await manager.createScope('auth', { name: 'Auth' });
668
-
668
+
669
669
  await context.setScope('auth');
670
-
670
+
671
671
  const scopeFile = path.join(tmpDir, '.bmad-scope');
672
672
  assertTrue(fs.existsSync(scopeFile), '.bmad-scope file should be created');
673
673
  } finally {
@@ -682,10 +682,10 @@ function testScopeContext() {
682
682
  const manager = new ScopeManager({ projectRoot: tmpDir });
683
683
  await manager.initialize();
684
684
  await manager.createScope('auth', { name: 'Auth' });
685
-
685
+
686
686
  await context.setScope('auth');
687
687
  const current = await context.getCurrentScope();
688
-
688
+
689
689
  assertEqual(current, 'auth', 'Should return session scope');
690
690
  } finally {
691
691
  teardown();
@@ -698,10 +698,10 @@ function testScopeContext() {
698
698
  const manager = new ScopeManager({ projectRoot: tmpDir });
699
699
  await manager.initialize();
700
700
  await manager.createScope('auth', { name: 'Auth' });
701
-
701
+
702
702
  await context.setScope('auth');
703
703
  await context.clearScope();
704
-
704
+
705
705
  const current = await context.getCurrentScope();
706
706
  assertEqual(current, null, 'Should return null after clearing');
707
707
  } finally {
@@ -715,22 +715,16 @@ function testScopeContext() {
715
715
  const manager = new ScopeManager({ projectRoot: tmpDir });
716
716
  await manager.initialize();
717
717
  await manager.createScope('auth', { name: 'Auth' });
718
-
718
+
719
719
  // Create global context
720
- fs.writeFileSync(
721
- path.join(tmpDir, '_bmad-output', '_shared', 'project-context.md'),
722
- '# Global Project\n\nGlobal content here.'
723
- );
724
-
720
+ fs.writeFileSync(path.join(tmpDir, '_bmad-output', '_shared', 'project-context.md'), '# Global Project\n\nGlobal content here.');
721
+
725
722
  // Create scope-specific context
726
723
  fs.mkdirSync(path.join(tmpDir, '_bmad-output', 'auth'), { recursive: true });
727
- fs.writeFileSync(
728
- path.join(tmpDir, '_bmad-output', 'auth', 'project-context.md'),
729
- '# Auth Scope\n\nScope-specific content.'
730
- );
731
-
724
+ fs.writeFileSync(path.join(tmpDir, '_bmad-output', 'auth', 'project-context.md'), '# Auth Scope\n\nScope-specific content.');
725
+
732
726
  const merged = await context.loadProjectContext('auth');
733
-
727
+
734
728
  assertTrue(merged.includes('Global content'), 'Should include global content');
735
729
  assertTrue(merged.includes('Scope-specific content'), 'Should include scope content');
736
730
  } finally {