bmad-fh 6.0.0-alpha.23.599980af → 6.0.0-alpha.23.6390fcb0

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.
@@ -390,6 +390,603 @@ async function testConcurrentLockSimulation() {
390
390
  }
391
391
  }
392
392
 
393
+ // ============================================================================
394
+ // E2E Test: Help Commands
395
+ // ============================================================================
396
+
397
+ async function testHelpCommandsE2E() {
398
+ console.log(`\n${colors.blue}E2E: Help Commands${colors.reset}`);
399
+
400
+ const { execSync } = require('node:child_process');
401
+ const cliPath = path.join(__dirname, '..', 'tools', 'cli', 'bmad-cli.js');
402
+
403
+ await asyncTest('scope --help shows subcommands', () => {
404
+ const output = execSync(`node ${cliPath} scope --help`, { encoding: 'utf8' });
405
+ assertTrue(output.includes('SUBCOMMANDS'), 'Should show SUBCOMMANDS section');
406
+ assertTrue(output.includes('init'), 'Should mention init');
407
+ assertTrue(output.includes('create'), 'Should mention create');
408
+ assertTrue(output.includes('list'), 'Should mention list');
409
+ });
410
+
411
+ await asyncTest('scope -h shows same as --help', () => {
412
+ const output = execSync(`node ${cliPath} scope -h`, { encoding: 'utf8' });
413
+ assertTrue(output.includes('SUBCOMMANDS'), 'Should show SUBCOMMANDS section');
414
+ });
415
+
416
+ await asyncTest('scope help shows comprehensive documentation', () => {
417
+ const output = execSync(`node ${cliPath} scope help`, { encoding: 'utf8' });
418
+ assertTrue(output.includes('OVERVIEW'), 'Should show OVERVIEW section');
419
+ assertTrue(output.includes('COMMANDS'), 'Should show COMMANDS section');
420
+ assertTrue(output.includes('QUICK START'), 'Should show QUICK START section');
421
+ });
422
+
423
+ await asyncTest('scope help create shows detailed create help', () => {
424
+ const output = execSync(`node ${cliPath} scope help create`, { encoding: 'utf8' });
425
+ assertTrue(output.includes('bmad scope create'), 'Should show create command title');
426
+ assertTrue(output.includes('ARGUMENTS'), 'Should show ARGUMENTS section');
427
+ assertTrue(output.includes('OPTIONS'), 'Should show OPTIONS section');
428
+ });
429
+
430
+ await asyncTest('scope help init shows detailed init help', () => {
431
+ const output = execSync(`node ${cliPath} scope help init`, { encoding: 'utf8' });
432
+ assertTrue(output.includes('bmad scope init'), 'Should show init command title');
433
+ assertTrue(output.includes('DESCRIPTION'), 'Should show DESCRIPTION section');
434
+ });
435
+
436
+ await asyncTest('scope help with invalid subcommand shows error', () => {
437
+ const output = execSync(`node ${cliPath} scope help invalidcommand`, { encoding: 'utf8' });
438
+ assertTrue(output.includes('Unknown command'), 'Should show unknown command error');
439
+ });
440
+
441
+ await asyncTest('scope help works with aliases', () => {
442
+ const output = execSync(`node ${cliPath} scope help ls`, { encoding: 'utf8' });
443
+ assertTrue(output.includes('bmad scope list'), 'Should show list help for ls alias');
444
+ });
445
+ }
446
+
447
+ // ============================================================================
448
+ // E2E Test: Error Handling and Edge Cases
449
+ // ============================================================================
450
+
451
+ async function testErrorHandlingE2E() {
452
+ console.log(`\n${colors.blue}E2E: Error Handling and Edge Cases${colors.reset}`);
453
+
454
+ const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
455
+ const { ScopeContext } = require('../src/core/lib/scope/scope-context');
456
+ const { ScopeSync } = require('../src/core/lib/scope/scope-sync');
457
+
458
+ let tmpDir;
459
+
460
+ try {
461
+ tmpDir = createTestProject();
462
+ const manager = new ScopeManager({ projectRoot: tmpDir });
463
+
464
+ // ========================================
465
+ // Error: Operations on uninitialized system
466
+ // ========================================
467
+ await asyncTest('List scopes on uninitialized system returns empty', async () => {
468
+ // Don't initialize, just try to list
469
+ let result = [];
470
+ try {
471
+ result = await manager.listScopes();
472
+ } catch {
473
+ // Expected - system not initialized
474
+ result = [];
475
+ }
476
+ assertEqual(result.length, 0, 'Should return empty or throw');
477
+ });
478
+
479
+ // Initialize for remaining tests
480
+ await manager.initialize();
481
+
482
+ // ========================================
483
+ // Error: Duplicate scope creation
484
+ // ========================================
485
+ await asyncTest('Creating duplicate scope throws meaningful error', async () => {
486
+ await manager.createScope('duptest', { name: 'Dup Test' });
487
+
488
+ let errorMsg = '';
489
+ try {
490
+ await manager.createScope('duptest', { name: 'Dup Test 2' });
491
+ } catch (error) {
492
+ errorMsg = error.message;
493
+ }
494
+ assertTrue(errorMsg.includes('already exists') || errorMsg.includes('duplicate'), `Error should mention duplicate: ${errorMsg}`);
495
+ });
496
+
497
+ // ========================================
498
+ // Error: Invalid operations on archived scope
499
+ // ========================================
500
+ await asyncTest('Operations on archived scope work correctly', async () => {
501
+ await manager.createScope('archtest', { name: 'Archive Test' });
502
+ await manager.archiveScope('archtest');
503
+
504
+ // Should still be able to get info
505
+ const scope = await manager.getScope('archtest');
506
+ assertEqual(scope.status, 'archived', 'Should get archived scope');
507
+
508
+ // Activate should work
509
+ await manager.activateScope('archtest');
510
+ const reactivated = await manager.getScope('archtest');
511
+ assertEqual(reactivated.status, 'active', 'Should be reactivated');
512
+ });
513
+
514
+ // ========================================
515
+ // Edge: Scope with maximum valid name length
516
+ // ========================================
517
+ await asyncTest('Scope with maximum length name', async () => {
518
+ const longName = 'A'.repeat(200); // Very long name
519
+ const scope = await manager.createScope('longname', {
520
+ name: longName,
521
+ description: 'B'.repeat(500),
522
+ });
523
+ assertEqual(scope.id, 'longname', 'Should create scope with long name');
524
+ });
525
+
526
+ // ========================================
527
+ // Edge: Scope with special characters in name/description
528
+ // ========================================
529
+ await asyncTest('Scope with special characters in metadata', async () => {
530
+ const scope = await manager.createScope('specialchars', {
531
+ name: 'Test <script>alert("xss")</script>',
532
+ description: 'Description with "quotes" and \'apostrophes\' and `backticks`',
533
+ });
534
+ assertEqual(scope.id, 'specialchars', 'Should create scope with special chars in metadata');
535
+ });
536
+
537
+ // ========================================
538
+ // Edge: Empty dependencies array
539
+ // ========================================
540
+ await asyncTest('Scope with empty dependencies array', async () => {
541
+ const scope = await manager.createScope('nodeps', {
542
+ name: 'No Deps',
543
+ dependencies: [],
544
+ });
545
+ assertEqual(scope.dependencies.length, 0, 'Should have no dependencies');
546
+ });
547
+
548
+ // ========================================
549
+ // Sync operations on non-existent scope - documents current behavior
550
+ // ========================================
551
+ await asyncTest('Sync-up on non-existent scope handles gracefully', async () => {
552
+ const sync = new ScopeSync({ projectRoot: tmpDir });
553
+
554
+ // Current implementation may return empty result or throw
555
+ // This documents actual behavior
556
+ let result = null;
557
+ try {
558
+ result = await sync.syncUp('nonexistent');
559
+ // If it doesn't throw, result should indicate no files synced
560
+ assertTrue(result.promoted.length === 0 || result.success !== false, 'Should handle gracefully with no files to sync');
561
+ } catch {
562
+ // Throwing is also acceptable behavior
563
+ assertTrue(true, 'Throws for non-existent scope');
564
+ }
565
+ });
566
+
567
+ // ========================================
568
+ // Edge: Rapid scope status changes
569
+ // ========================================
570
+ await asyncTest('Rapid archive/activate cycles', async () => {
571
+ await manager.createScope('rapidcycle', { name: 'Rapid Cycle' });
572
+
573
+ for (let i = 0; i < 5; i++) {
574
+ await manager.archiveScope('rapidcycle');
575
+ await manager.activateScope('rapidcycle');
576
+ }
577
+
578
+ const scope = await manager.getScope('rapidcycle');
579
+ assertEqual(scope.status, 'active', 'Should end up active after cycles');
580
+ });
581
+ } finally {
582
+ if (tmpDir) {
583
+ cleanupTestProject(tmpDir);
584
+ }
585
+ }
586
+ }
587
+
588
+ // ============================================================================
589
+ // E2E Test: Complex Dependency Scenarios
590
+ // ============================================================================
591
+
592
+ async function testComplexDependencyE2E() {
593
+ console.log(`\n${colors.blue}E2E: Complex Dependency Scenarios${colors.reset}`);
594
+
595
+ const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
596
+
597
+ let tmpDir;
598
+
599
+ try {
600
+ tmpDir = createTestProject();
601
+ const manager = new ScopeManager({ projectRoot: tmpDir });
602
+ await manager.initialize();
603
+
604
+ // ========================================
605
+ // Diamond dependency pattern
606
+ // ========================================
607
+ await asyncTest('Diamond dependency pattern works', async () => {
608
+ // core
609
+ // / \
610
+ // auth user
611
+ // \ /
612
+ // payments
613
+ await manager.createScope('core', { name: 'Core' });
614
+ await manager.createScope('auth', { name: 'Auth', dependencies: ['core'] });
615
+ await manager.createScope('user', { name: 'User', dependencies: ['core'] });
616
+ await manager.createScope('payments', { name: 'Payments', dependencies: ['auth', 'user'] });
617
+
618
+ const payments = await manager.getScope('payments');
619
+ assertTrue(payments.dependencies.includes('auth'), 'Should depend on auth');
620
+ assertTrue(payments.dependencies.includes('user'), 'Should depend on user');
621
+ });
622
+
623
+ // ========================================
624
+ // Finding all dependents in complex graph
625
+ // ========================================
626
+ await asyncTest('Finds all dependents in complex graph', async () => {
627
+ const coreDependents = await manager.findDependentScopes('core');
628
+ assertTrue(coreDependents.includes('auth'), 'auth should depend on core');
629
+ assertTrue(coreDependents.includes('user'), 'user should depend on core');
630
+ // Transitive dependents may or may not be included depending on implementation
631
+ });
632
+
633
+ // ========================================
634
+ // Removing scope in middle of dependency chain
635
+ // ========================================
636
+ await asyncTest('Cannot remove scope with dependents without force', async () => {
637
+ let threw = false;
638
+ try {
639
+ await manager.removeScope('auth'); // payments depends on auth
640
+ } catch {
641
+ threw = true;
642
+ }
643
+ assertTrue(threw, 'Should throw when removing scope with dependents');
644
+ });
645
+
646
+ // ========================================
647
+ // Adding dependency to existing scope
648
+ // ========================================
649
+ await asyncTest('Adding new dependency to existing scope', async () => {
650
+ await manager.createScope('notifications', { name: 'Notifications' });
651
+ await manager.updateScope('payments', {
652
+ dependencies: ['auth', 'user', 'notifications'],
653
+ });
654
+
655
+ const payments = await manager.getScope('payments');
656
+ assertTrue(payments.dependencies.includes('notifications'), 'Should have new dependency');
657
+ });
658
+
659
+ // ========================================
660
+ // Archiving scope in dependency chain
661
+ // ========================================
662
+ await asyncTest('Archiving scope in dependency chain', async () => {
663
+ await manager.archiveScope('auth');
664
+
665
+ // Payments should still exist and have auth as dependency
666
+ const payments = await manager.getScope('payments');
667
+ assertTrue(payments.dependencies.includes('auth'), 'Dependency should remain');
668
+
669
+ // Reactivate for cleanup
670
+ await manager.activateScope('auth');
671
+ });
672
+ } finally {
673
+ if (tmpDir) {
674
+ cleanupTestProject(tmpDir);
675
+ }
676
+ }
677
+ }
678
+
679
+ // ============================================================================
680
+ // E2E Test: Sync Operations Edge Cases
681
+ // ============================================================================
682
+
683
+ async function testSyncOperationsE2E() {
684
+ console.log(`\n${colors.blue}E2E: Sync Operations Edge Cases${colors.reset}`);
685
+
686
+ const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
687
+ const { ScopeSync } = require('../src/core/lib/scope/scope-sync');
688
+
689
+ let tmpDir;
690
+
691
+ try {
692
+ tmpDir = createTestProject();
693
+ const manager = new ScopeManager({ projectRoot: tmpDir });
694
+ await manager.initialize();
695
+
696
+ const sync = new ScopeSync({ projectRoot: tmpDir });
697
+
698
+ // Create test scope
699
+ await manager.createScope('synctest', { name: 'Sync Test' });
700
+
701
+ // ========================================
702
+ // Sync-up with no promotable files
703
+ // ========================================
704
+ await asyncTest('Sync-up with no promotable files', async () => {
705
+ // Create non-promotable file
706
+ const filePath = path.join(tmpDir, '_bmad-output', 'synctest', 'planning-artifacts', 'notes.md');
707
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
708
+ fs.writeFileSync(filePath, '# Random Notes');
709
+
710
+ const result = await sync.syncUp('synctest');
711
+ // Should succeed but with no files promoted
712
+ assertTrue(result.success || result.promoted.length === 0, 'Should handle no promotable files');
713
+ });
714
+
715
+ // ========================================
716
+ // Sync-up with empty architecture directory
717
+ // ========================================
718
+ await asyncTest('Sync-up with empty promotable directory', async () => {
719
+ // Create empty architecture directory
720
+ fs.mkdirSync(path.join(tmpDir, '_bmad-output', 'synctest', 'architecture'), { recursive: true });
721
+
722
+ const result = await sync.syncUp('synctest');
723
+ assertTrue(result.success !== false, 'Should handle empty directory');
724
+ });
725
+
726
+ // ========================================
727
+ // Sync-up with binary files (should skip)
728
+ // ========================================
729
+ await asyncTest('Sync-up skips binary files', async () => {
730
+ // Create a file that might be considered binary
731
+ const archPath = path.join(tmpDir, '_bmad-output', 'synctest', 'architecture', 'diagram.png');
732
+ fs.mkdirSync(path.dirname(archPath), { recursive: true });
733
+ fs.writeFileSync(archPath, Buffer.from([0x89, 0x50, 0x4e, 0x47])); // PNG header
734
+
735
+ const result = await sync.syncUp('synctest');
736
+ // Should succeed, binary might be skipped or included depending on implementation
737
+ assertTrue(result.success !== false, 'Should handle binary files gracefully');
738
+ });
739
+
740
+ // ========================================
741
+ // Create scope when directory already exists - safe by default
742
+ // ========================================
743
+ await asyncTest('Creating scope when directory exists throws by default', async () => {
744
+ // Pre-create the directory
745
+ fs.mkdirSync(path.join(tmpDir, '_bmad-output', 'preexist', 'planning-artifacts'), { recursive: true });
746
+ fs.writeFileSync(path.join(tmpDir, '_bmad-output', 'preexist', 'planning-artifacts', 'existing.md'), '# Existing File');
747
+
748
+ // Create scope - should throw because directory exists (safe default)
749
+ let threw = false;
750
+ let errorMsg = '';
751
+ try {
752
+ await manager.createScope('preexist', { name: 'Pre-existing' });
753
+ } catch (error) {
754
+ threw = true;
755
+ errorMsg = error.message;
756
+ }
757
+
758
+ assertTrue(threw, 'Should throw when directory exists');
759
+ assertTrue(errorMsg.includes('already exists'), 'Error should mention directory exists');
760
+
761
+ // Existing file should still be preserved
762
+ const existingContent = fs.readFileSync(path.join(tmpDir, '_bmad-output', 'preexist', 'planning-artifacts', 'existing.md'), 'utf8');
763
+ assertTrue(existingContent.includes('Existing File'), 'Should preserve existing files');
764
+ });
765
+
766
+ // ========================================
767
+ // Sync with very long file paths
768
+ // ========================================
769
+ await asyncTest('Sync handles deeply nested paths', async () => {
770
+ const deepPath = path.join(tmpDir, '_bmad-output', 'synctest', 'architecture', 'deep', 'nested', 'structure', 'document.md');
771
+ fs.mkdirSync(path.dirname(deepPath), { recursive: true });
772
+ fs.writeFileSync(deepPath, '# Deeply Nested');
773
+
774
+ const result = await sync.syncUp('synctest');
775
+ assertTrue(result.success !== false, 'Should handle deep paths');
776
+ });
777
+
778
+ // ========================================
779
+ // Sync with special characters in filename
780
+ // ========================================
781
+ await asyncTest('Sync handles special characters in filenames', async () => {
782
+ const specialPath = path.join(tmpDir, '_bmad-output', 'synctest', 'architecture', 'design (v2) [draft].md');
783
+ fs.mkdirSync(path.dirname(specialPath), { recursive: true });
784
+ fs.writeFileSync(specialPath, '# Design v2 Draft');
785
+
786
+ const result = await sync.syncUp('synctest');
787
+ assertTrue(result.success !== false, 'Should handle special chars in filenames');
788
+ });
789
+ } finally {
790
+ if (tmpDir) {
791
+ cleanupTestProject(tmpDir);
792
+ }
793
+ }
794
+ }
795
+
796
+ // ============================================================================
797
+ // E2E Test: File System Edge Cases
798
+ // ============================================================================
799
+
800
+ async function testFileSystemEdgeCasesE2E() {
801
+ console.log(`\n${colors.blue}E2E: File System Edge Cases${colors.reset}`);
802
+
803
+ const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
804
+ const { ScopeInitializer } = require('../src/core/lib/scope/scope-initializer');
805
+
806
+ let tmpDir;
807
+
808
+ try {
809
+ tmpDir = createTestProject();
810
+ const manager = new ScopeManager({ projectRoot: tmpDir });
811
+ const initializer = new ScopeInitializer({ projectRoot: tmpDir });
812
+ await manager.initialize();
813
+
814
+ // ========================================
815
+ // Remove scope with readonly files
816
+ // ========================================
817
+ await asyncTest('Remove scope handles readonly files', async () => {
818
+ await manager.createScope('readonly', { name: 'Readonly Test' });
819
+
820
+ // Make a file readonly
821
+ const filePath = path.join(tmpDir, '_bmad-output', 'readonly', 'planning-artifacts', 'locked.md');
822
+ fs.writeFileSync(filePath, '# Locked');
823
+ try {
824
+ fs.chmodSync(filePath, 0o444); // Read-only
825
+ } catch {
826
+ // Windows might not support chmod
827
+ }
828
+
829
+ // Remove should handle this gracefully
830
+ let removed = false;
831
+ try {
832
+ await initializer.removeScope('readonly', { backup: false });
833
+ await manager.removeScope('readonly', { force: true });
834
+ removed = true;
835
+ } catch {
836
+ // May fail on some systems, that's ok
837
+ // Clean up by making it writable again
838
+ try {
839
+ fs.chmodSync(filePath, 0o644);
840
+ await initializer.removeScope('readonly', { backup: false });
841
+ await manager.removeScope('readonly', { force: true });
842
+ removed = true;
843
+ } catch {
844
+ // Ignore cleanup errors
845
+ }
846
+ }
847
+ // Just verify it attempted the operation
848
+ assertTrue(true, 'Attempted removal of readonly files');
849
+ });
850
+
851
+ // ========================================
852
+ // Scope with symlinks (if supported)
853
+ // ========================================
854
+ await asyncTest('Scope handles symlinks gracefully', async () => {
855
+ await manager.createScope('symtest', { name: 'Symlink Test' });
856
+
857
+ const targetPath = path.join(tmpDir, '_bmad-output', 'symtest', 'planning-artifacts', 'target.md');
858
+ const linkPath = path.join(tmpDir, '_bmad-output', 'symtest', 'planning-artifacts', 'link.md');
859
+
860
+ fs.writeFileSync(targetPath, '# Target');
861
+
862
+ try {
863
+ fs.symlinkSync(targetPath, linkPath);
864
+
865
+ // Should be able to read through symlink
866
+ const content = fs.readFileSync(linkPath, 'utf8');
867
+ assertTrue(content.includes('Target'), 'Should read through symlink');
868
+ } catch {
869
+ // Symlinks may not be supported on all systems
870
+ assertTrue(true, 'Symlinks not supported on this system');
871
+ }
872
+ });
873
+
874
+ // ========================================
875
+ // Large number of files in scope
876
+ // ========================================
877
+ await asyncTest('Scope with many files', async () => {
878
+ await manager.createScope('manyfiles', { name: 'Many Files' });
879
+
880
+ const planningDir = path.join(tmpDir, '_bmad-output', 'manyfiles', 'planning-artifacts');
881
+
882
+ // Create 100 files
883
+ for (let i = 0; i < 100; i++) {
884
+ fs.writeFileSync(path.join(planningDir, `file-${i}.md`), `# File ${i}`);
885
+ }
886
+
887
+ // Should still be able to manage scope
888
+ const scope = await manager.getScope('manyfiles');
889
+ assertEqual(scope.id, 'manyfiles', 'Should manage scope with many files');
890
+ });
891
+ } finally {
892
+ if (tmpDir) {
893
+ cleanupTestProject(tmpDir);
894
+ }
895
+ }
896
+ }
897
+
898
+ // ============================================================================
899
+ // E2E Test: Concurrent Operations Stress Test
900
+ // ============================================================================
901
+
902
+ async function testConcurrentOperationsE2E() {
903
+ console.log(`\n${colors.blue}E2E: Concurrent Operations Stress Test${colors.reset}`);
904
+
905
+ const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
906
+ const { ScopeContext } = require('../src/core/lib/scope/scope-context');
907
+
908
+ let tmpDir;
909
+
910
+ try {
911
+ tmpDir = createTestProject();
912
+ const manager = new ScopeManager({ projectRoot: tmpDir });
913
+ await manager.initialize();
914
+
915
+ // ========================================
916
+ // Concurrent scope creation stress test
917
+ // ========================================
918
+ await asyncTest('Concurrent scope creations (stress test)', async () => {
919
+ const createPromises = [];
920
+ for (let i = 0; i < 20; i++) {
921
+ createPromises.push(
922
+ manager
923
+ .createScope(`concurrent-${i}`, { name: `Concurrent ${i}` })
924
+ .catch((error) => ({ error: error.message, id: `concurrent-${i}` })),
925
+ );
926
+ }
927
+
928
+ const results = await Promise.all(createPromises);
929
+
930
+ // Count successes
931
+ const successes = results.filter((r) => !r.error);
932
+ assertTrue(successes.length > 0, 'At least some concurrent creates should succeed');
933
+
934
+ // Verify all created scopes exist
935
+ const scopes = await manager.listScopes();
936
+ assertTrue(scopes.length >= successes.length, 'All successful creates should persist');
937
+ });
938
+
939
+ // ========================================
940
+ // Concurrent read/write operations
941
+ // ========================================
942
+ await asyncTest('Concurrent reads during writes', async () => {
943
+ await manager.createScope('rwtest', { name: 'Read/Write Test' });
944
+
945
+ const operations = [];
946
+
947
+ // Mix of reads and writes
948
+ for (let i = 0; i < 10; i++) {
949
+ if (i % 2 === 0) {
950
+ // Read
951
+ operations.push(manager.getScope('rwtest'));
952
+ } else {
953
+ // Write (update)
954
+ operations.push(manager.updateScope('rwtest', { description: `Update ${i}` }));
955
+ }
956
+ }
957
+
958
+ await Promise.all(operations);
959
+
960
+ // Verify scope is still valid
961
+ const scope = await manager.getScope('rwtest');
962
+ assertEqual(scope.id, 'rwtest', 'Scope should still be valid');
963
+ });
964
+
965
+ // ========================================
966
+ // Concurrent context switches
967
+ // ========================================
968
+ await asyncTest('Concurrent context switches', async () => {
969
+ const context1 = new ScopeContext({ projectRoot: tmpDir });
970
+ const context2 = new ScopeContext({ projectRoot: tmpDir });
971
+
972
+ // Both try to set different scopes
973
+ const [, scope1, scope2] = await Promise.all([
974
+ manager.createScope('ctx1', { name: 'Context 1' }),
975
+ context1.setScope('rwtest').then(() => context1.getCurrentScope()),
976
+ context2.setScope('rwtest').then(() => context2.getCurrentScope()),
977
+ ]);
978
+
979
+ // One should win (last write wins)
980
+ const finalScope = await context1.getCurrentScope();
981
+ assertTrue(finalScope === 'rwtest', 'Should have a valid scope set');
982
+ });
983
+ } finally {
984
+ if (tmpDir) {
985
+ cleanupTestProject(tmpDir);
986
+ }
987
+ }
988
+ }
989
+
393
990
  // ============================================================================
394
991
  // Main Runner
395
992
  // ============================================================================
@@ -402,6 +999,14 @@ async function main() {
402
999
  try {
403
1000
  await testParallelScopeWorkflow();
404
1001
  await testConcurrentLockSimulation();
1002
+
1003
+ // New comprehensive E2E tests
1004
+ await testHelpCommandsE2E();
1005
+ await testErrorHandlingE2E();
1006
+ await testComplexDependencyE2E();
1007
+ await testSyncOperationsE2E();
1008
+ await testFileSystemEdgeCasesE2E();
1009
+ await testConcurrentOperationsE2E();
405
1010
  } catch (error) {
406
1011
  console.log(`\n${colors.red}Fatal error: ${error.message}${colors.reset}`);
407
1012
  console.log(error.stack);