@zigrivers/scaffold 3.7.0 → 3.9.0
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/README.md +113 -8
- package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
- package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
- package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
- package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
- package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
- package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
- package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
- package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
- package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
- package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
- package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
- package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
- package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
- package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
- package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
- package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
- package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
- package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
- package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
- package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
- package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
- package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
- package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
- package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
- package/content/knowledge/library/library-api-design.md +306 -0
- package/content/knowledge/library/library-architecture.md +247 -0
- package/content/knowledge/library/library-bundling.md +244 -0
- package/content/knowledge/library/library-conventions.md +229 -0
- package/content/knowledge/library/library-dev-environment.md +220 -0
- package/content/knowledge/library/library-documentation.md +300 -0
- package/content/knowledge/library/library-project-structure.md +237 -0
- package/content/knowledge/library/library-requirements.md +173 -0
- package/content/knowledge/library/library-security.md +257 -0
- package/content/knowledge/library/library-testing.md +319 -0
- package/content/knowledge/library/library-type-definitions.md +284 -0
- package/content/knowledge/library/library-versioning.md +300 -0
- package/content/knowledge/ml/ml-architecture.md +172 -0
- package/content/knowledge/ml/ml-conventions.md +209 -0
- package/content/knowledge/ml/ml-dev-environment.md +299 -0
- package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
- package/content/knowledge/ml/ml-model-evaluation.md +256 -0
- package/content/knowledge/ml/ml-observability.md +253 -0
- package/content/knowledge/ml/ml-project-structure.md +216 -0
- package/content/knowledge/ml/ml-requirements.md +138 -0
- package/content/knowledge/ml/ml-security.md +188 -0
- package/content/knowledge/ml/ml-serving-patterns.md +243 -0
- package/content/knowledge/ml/ml-testing.md +301 -0
- package/content/knowledge/ml/ml-training-patterns.md +269 -0
- package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
- package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
- package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
- package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
- package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
- package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
- package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
- package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
- package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
- package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
- package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
- package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
- package/content/methodology/browser-extension-overlay.yml +82 -0
- package/content/methodology/data-pipeline-overlay.yml +70 -0
- package/content/methodology/library-overlay.yml +67 -0
- package/content/methodology/ml-overlay.yml +70 -0
- package/content/methodology/mobile-app-overlay.yml +71 -0
- package/dist/cli/commands/init.d.ts +22 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +202 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +190 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +1456 -80
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +87 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +312 -3
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +55 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +2 -1
- package/dist/e2e/project-type-overlays.test.d.ts.map +1 -1
- package/dist/e2e/project-type-overlays.test.js +780 -14
- package/dist/e2e/project-type-overlays.test.js.map +1 -1
- package/dist/types/config.d.ts +16 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/wizard/questions.d.ts +28 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +127 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +224 -4
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +22 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +28 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* E2E integration tests for project-type overlay flow:
|
|
3
3
|
* init → config.yml → overlay resolution → knowledge injection
|
|
4
4
|
*
|
|
5
|
-
* Tests the full pipeline for web-app, backend,
|
|
5
|
+
* Tests the full pipeline for web-app, backend, cli, library, mobile-app,
|
|
6
|
+
* data-pipeline, ml, and browser-extension project types:
|
|
6
7
|
* 1. Init creates config with project-type-specific config block
|
|
7
8
|
* 2. Config validates through ConfigSchema
|
|
8
9
|
* 3. Overlay loads and resolves against real pipeline meta-prompts
|
|
@@ -485,28 +486,708 @@ describe('cli overlay integration', () => {
|
|
|
485
486
|
});
|
|
486
487
|
});
|
|
487
488
|
// ---------------------------------------------------------------------------
|
|
489
|
+
// Tests — Library
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
describe('library overlay integration', () => {
|
|
492
|
+
let tmpDir;
|
|
493
|
+
beforeEach(() => {
|
|
494
|
+
tmpDir = makeTempDir();
|
|
495
|
+
});
|
|
496
|
+
afterEach(() => {
|
|
497
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
498
|
+
vi.restoreAllMocks();
|
|
499
|
+
});
|
|
500
|
+
// Test 1: Config with library + libraryConfig validates through ConfigSchema
|
|
501
|
+
it('library config with libraryConfig validates through ConfigSchema', () => {
|
|
502
|
+
const result = ConfigSchema.safeParse({
|
|
503
|
+
version: 2,
|
|
504
|
+
methodology: 'deep',
|
|
505
|
+
platforms: ['claude-code'],
|
|
506
|
+
project: {
|
|
507
|
+
projectType: 'library',
|
|
508
|
+
libraryConfig: { visibility: 'public' },
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
expect(result.success).toBe(true);
|
|
512
|
+
if (result.success) {
|
|
513
|
+
const project = result.data.project;
|
|
514
|
+
expect(project['projectType']).toBe('library');
|
|
515
|
+
const lc = project['libraryConfig'];
|
|
516
|
+
expect(lc['visibility']).toBe('public');
|
|
517
|
+
expect(lc['runtimeTarget']).toBe('isomorphic'); // default
|
|
518
|
+
expect(lc['bundleFormat']).toBe('dual'); // default
|
|
519
|
+
expect(lc['hasTypeDefinitions']).toBe(true); // default
|
|
520
|
+
expect(lc['documentationLevel']).toBe('readme'); // default
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
// Test 2: Init with projectType library creates config with libraryConfig
|
|
524
|
+
it('init with projectType library creates config.yml with libraryConfig defaults', async () => {
|
|
525
|
+
const output = createMockOutput();
|
|
526
|
+
const result = await runWizard({
|
|
527
|
+
projectRoot: tmpDir,
|
|
528
|
+
projectType: 'library',
|
|
529
|
+
libVisibility: 'public',
|
|
530
|
+
methodology: 'deep',
|
|
531
|
+
force: false,
|
|
532
|
+
auto: true,
|
|
533
|
+
output,
|
|
534
|
+
});
|
|
535
|
+
expect(result.success).toBe(true);
|
|
536
|
+
const { config } = loadConfig(tmpDir, []);
|
|
537
|
+
expect(config).not.toBeNull();
|
|
538
|
+
expect(config.project?.projectType).toBe('library');
|
|
539
|
+
expect(config.project?.libraryConfig).toBeDefined();
|
|
540
|
+
expect(config.project?.libraryConfig?.visibility).toBe('public');
|
|
541
|
+
});
|
|
542
|
+
// Test 3: config.yml round-trips through YAML correctly
|
|
543
|
+
it('config.yml round-trips projectType and libraryConfig through YAML', async () => {
|
|
544
|
+
const output = createMockOutput();
|
|
545
|
+
await runWizard({
|
|
546
|
+
projectRoot: tmpDir,
|
|
547
|
+
projectType: 'library',
|
|
548
|
+
libVisibility: 'internal',
|
|
549
|
+
methodology: 'deep',
|
|
550
|
+
force: false,
|
|
551
|
+
auto: true,
|
|
552
|
+
output,
|
|
553
|
+
});
|
|
554
|
+
const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
|
|
555
|
+
const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
556
|
+
const project = raw['project'];
|
|
557
|
+
expect(project['projectType']).toBe('library');
|
|
558
|
+
expect(project['libraryConfig']).toBeDefined();
|
|
559
|
+
const lc = project['libraryConfig'];
|
|
560
|
+
expect(lc['visibility']).toBe('internal');
|
|
561
|
+
});
|
|
562
|
+
// Test 4: Overlay loads successfully from content/methodology
|
|
563
|
+
it('library overlay loads without errors', () => {
|
|
564
|
+
const methodologyDir = getPackageMethodologyDir();
|
|
565
|
+
const overlayPath = path.join(methodologyDir, 'library-overlay.yml');
|
|
566
|
+
const { overlay, errors } = loadOverlay(overlayPath);
|
|
567
|
+
expect(errors).toHaveLength(0);
|
|
568
|
+
expect(overlay).not.toBeNull();
|
|
569
|
+
expect(overlay.projectType).toBe('library');
|
|
570
|
+
expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
|
|
571
|
+
});
|
|
572
|
+
// Test 5: Overlay injects library knowledge into architecture step
|
|
573
|
+
it('overlay injects library-architecture into system-architecture step', async () => {
|
|
574
|
+
const { overlayState } = await resolveProjectOverlay('library');
|
|
575
|
+
expect(overlayState.knowledge['system-architecture']).toBeDefined();
|
|
576
|
+
expect(overlayState.knowledge['system-architecture']).toContain('library-architecture');
|
|
577
|
+
});
|
|
578
|
+
// Test 6: Overlay injects knowledge into tech-stack step
|
|
579
|
+
it('overlay injects library knowledge into tech-stack step', async () => {
|
|
580
|
+
const { overlayState } = await resolveProjectOverlay('library');
|
|
581
|
+
expect(overlayState.knowledge['tech-stack']).toBeDefined();
|
|
582
|
+
expect(overlayState.knowledge['tech-stack']).toContain('library-architecture');
|
|
583
|
+
expect(overlayState.knowledge['tech-stack']).toContain('library-bundling');
|
|
584
|
+
expect(overlayState.knowledge['tech-stack']).toContain('library-type-definitions');
|
|
585
|
+
});
|
|
586
|
+
// Test 7: Overlay injects knowledge into testing steps
|
|
587
|
+
it('overlay injects library-testing into TDD and e2e steps', async () => {
|
|
588
|
+
const { overlayState } = await resolveProjectOverlay('library');
|
|
589
|
+
expect(overlayState.knowledge['tdd']).toBeDefined();
|
|
590
|
+
expect(overlayState.knowledge['tdd']).toContain('library-testing');
|
|
591
|
+
expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
|
|
592
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('library-testing');
|
|
593
|
+
});
|
|
594
|
+
// Test 8: Overlay injects knowledge into foundational steps
|
|
595
|
+
it('overlay injects library knowledge into foundational steps', async () => {
|
|
596
|
+
const { overlayState } = await resolveProjectOverlay('library');
|
|
597
|
+
expect(overlayState.knowledge['create-prd']).toContain('library-requirements');
|
|
598
|
+
expect(overlayState.knowledge['coding-standards']).toContain('library-conventions');
|
|
599
|
+
expect(overlayState.knowledge['project-structure']).toContain('library-project-structure');
|
|
600
|
+
});
|
|
601
|
+
// Test 9: Overlay injects knowledge into api-contracts and operations steps
|
|
602
|
+
it('overlay injects library knowledge into api-contracts and operations steps', async () => {
|
|
603
|
+
const { overlayState } = await resolveProjectOverlay('library');
|
|
604
|
+
expect(overlayState.knowledge['api-contracts']).toBeDefined();
|
|
605
|
+
expect(overlayState.knowledge['api-contracts']).toContain('library-api-design');
|
|
606
|
+
expect(overlayState.knowledge['operations']).toBeDefined();
|
|
607
|
+
expect(overlayState.knowledge['operations']).toContain('library-versioning');
|
|
608
|
+
expect(overlayState.knowledge['operations']).toContain('library-documentation');
|
|
609
|
+
});
|
|
610
|
+
// Test 10: MVP methodology with library overlay works
|
|
611
|
+
it('MVP methodology with library overlay injects knowledge', async () => {
|
|
612
|
+
const { overlayState } = await resolveProjectOverlay('library', 'mvp');
|
|
613
|
+
expect(overlayState.knowledge['system-architecture']).toContain('library-architecture');
|
|
614
|
+
expect(overlayState.knowledge['tech-stack']).toContain('library-bundling');
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
// ---------------------------------------------------------------------------
|
|
618
|
+
// Tests — Mobile-app
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
describe('mobile-app overlay integration', () => {
|
|
621
|
+
let tmpDir;
|
|
622
|
+
beforeEach(() => {
|
|
623
|
+
tmpDir = makeTempDir();
|
|
624
|
+
});
|
|
625
|
+
afterEach(() => {
|
|
626
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
627
|
+
vi.restoreAllMocks();
|
|
628
|
+
});
|
|
629
|
+
// Test 1: Config with mobile-app + mobileAppConfig validates through ConfigSchema
|
|
630
|
+
it('mobile-app config with mobileAppConfig validates through ConfigSchema', () => {
|
|
631
|
+
const result = ConfigSchema.safeParse({
|
|
632
|
+
version: 2,
|
|
633
|
+
methodology: 'deep',
|
|
634
|
+
platforms: ['claude-code'],
|
|
635
|
+
project: {
|
|
636
|
+
projectType: 'mobile-app',
|
|
637
|
+
mobileAppConfig: { platform: 'cross-platform' },
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
expect(result.success).toBe(true);
|
|
641
|
+
if (result.success) {
|
|
642
|
+
const project = result.data.project;
|
|
643
|
+
expect(project['projectType']).toBe('mobile-app');
|
|
644
|
+
const mc = project['mobileAppConfig'];
|
|
645
|
+
expect(mc['platform']).toBe('cross-platform');
|
|
646
|
+
expect(mc['distributionModel']).toBe('public'); // default
|
|
647
|
+
expect(mc['offlineSupport']).toBe('none'); // default
|
|
648
|
+
expect(mc['hasPushNotifications']).toBe(false); // default
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
// Test 2: Init with projectType mobile-app creates config with mobileAppConfig
|
|
652
|
+
it('init with projectType mobile-app creates config.yml with mobileAppConfig defaults', async () => {
|
|
653
|
+
const output = createMockOutput();
|
|
654
|
+
const result = await runWizard({
|
|
655
|
+
projectRoot: tmpDir,
|
|
656
|
+
projectType: 'mobile-app',
|
|
657
|
+
mobilePlatform: 'cross-platform',
|
|
658
|
+
methodology: 'deep',
|
|
659
|
+
force: false,
|
|
660
|
+
auto: true,
|
|
661
|
+
output,
|
|
662
|
+
});
|
|
663
|
+
expect(result.success).toBe(true);
|
|
664
|
+
const { config } = loadConfig(tmpDir, []);
|
|
665
|
+
expect(config).not.toBeNull();
|
|
666
|
+
expect(config.project?.projectType).toBe('mobile-app');
|
|
667
|
+
expect(config.project?.mobileAppConfig).toBeDefined();
|
|
668
|
+
expect(config.project?.mobileAppConfig?.platform).toBe('cross-platform');
|
|
669
|
+
});
|
|
670
|
+
// Test 3: config.yml round-trips through YAML correctly
|
|
671
|
+
it('config.yml round-trips projectType and mobileAppConfig through YAML', async () => {
|
|
672
|
+
const output = createMockOutput();
|
|
673
|
+
await runWizard({
|
|
674
|
+
projectRoot: tmpDir,
|
|
675
|
+
projectType: 'mobile-app',
|
|
676
|
+
mobilePlatform: 'ios',
|
|
677
|
+
methodology: 'deep',
|
|
678
|
+
force: false,
|
|
679
|
+
auto: true,
|
|
680
|
+
output,
|
|
681
|
+
});
|
|
682
|
+
const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
|
|
683
|
+
const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
684
|
+
const project = raw['project'];
|
|
685
|
+
expect(project['projectType']).toBe('mobile-app');
|
|
686
|
+
expect(project['mobileAppConfig']).toBeDefined();
|
|
687
|
+
const mc = project['mobileAppConfig'];
|
|
688
|
+
expect(mc['platform']).toBe('ios');
|
|
689
|
+
});
|
|
690
|
+
// Test 4: Overlay loads successfully from content/methodology
|
|
691
|
+
it('mobile-app overlay loads without errors', () => {
|
|
692
|
+
const methodologyDir = getPackageMethodologyDir();
|
|
693
|
+
const overlayPath = path.join(methodologyDir, 'mobile-app-overlay.yml');
|
|
694
|
+
const { overlay, errors } = loadOverlay(overlayPath);
|
|
695
|
+
expect(errors).toHaveLength(0);
|
|
696
|
+
expect(overlay).not.toBeNull();
|
|
697
|
+
expect(overlay.projectType).toBe('mobile-app');
|
|
698
|
+
expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
|
|
699
|
+
});
|
|
700
|
+
// Test 5: Overlay injects mobile-app knowledge into architecture step
|
|
701
|
+
it('overlay injects mobile-app-architecture into system-architecture step', async () => {
|
|
702
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app');
|
|
703
|
+
expect(overlayState.knowledge['system-architecture']).toBeDefined();
|
|
704
|
+
expect(overlayState.knowledge['system-architecture']).toContain('mobile-app-architecture');
|
|
705
|
+
expect(overlayState.knowledge['system-architecture']).toContain('mobile-app-offline-patterns');
|
|
706
|
+
expect(overlayState.knowledge['system-architecture']).toContain('mobile-app-push-notifications');
|
|
707
|
+
});
|
|
708
|
+
// Test 6: Overlay injects knowledge into tech-stack step
|
|
709
|
+
it('overlay injects mobile-app knowledge into tech-stack step', async () => {
|
|
710
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app');
|
|
711
|
+
expect(overlayState.knowledge['tech-stack']).toBeDefined();
|
|
712
|
+
expect(overlayState.knowledge['tech-stack']).toContain('mobile-app-architecture');
|
|
713
|
+
expect(overlayState.knowledge['tech-stack']).toContain('mobile-app-deployment');
|
|
714
|
+
});
|
|
715
|
+
// Test 7: Overlay injects knowledge into testing steps
|
|
716
|
+
it('overlay injects mobile-app-testing into TDD and e2e steps', async () => {
|
|
717
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app');
|
|
718
|
+
expect(overlayState.knowledge['tdd']).toBeDefined();
|
|
719
|
+
expect(overlayState.knowledge['tdd']).toContain('mobile-app-testing');
|
|
720
|
+
expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
|
|
721
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('mobile-app-testing');
|
|
722
|
+
});
|
|
723
|
+
// Test 8: Overlay injects knowledge into foundational steps
|
|
724
|
+
it('overlay injects mobile-app knowledge into foundational steps', async () => {
|
|
725
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app');
|
|
726
|
+
expect(overlayState.knowledge['create-prd']).toContain('mobile-app-requirements');
|
|
727
|
+
expect(overlayState.knowledge['coding-standards']).toContain('mobile-app-conventions');
|
|
728
|
+
expect(overlayState.knowledge['project-structure']).toContain('mobile-app-project-structure');
|
|
729
|
+
});
|
|
730
|
+
// Test 9: Overlay injects knowledge into ux-spec and operations steps
|
|
731
|
+
it('overlay injects mobile-app knowledge into ux-spec and operations steps', async () => {
|
|
732
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app');
|
|
733
|
+
expect(overlayState.knowledge['ux-spec']).toBeDefined();
|
|
734
|
+
expect(overlayState.knowledge['ux-spec']).toContain('mobile-app-architecture');
|
|
735
|
+
expect(overlayState.knowledge['operations']).toBeDefined();
|
|
736
|
+
expect(overlayState.knowledge['operations']).toContain('mobile-app-deployment');
|
|
737
|
+
expect(overlayState.knowledge['operations']).toContain('mobile-app-distribution');
|
|
738
|
+
expect(overlayState.knowledge['operations']).toContain('mobile-app-observability');
|
|
739
|
+
});
|
|
740
|
+
// Test 10: MVP methodology with mobile-app overlay works
|
|
741
|
+
it('MVP methodology with mobile-app overlay injects knowledge', async () => {
|
|
742
|
+
const { overlayState } = await resolveProjectOverlay('mobile-app', 'mvp');
|
|
743
|
+
expect(overlayState.knowledge['system-architecture']).toContain('mobile-app-architecture');
|
|
744
|
+
expect(overlayState.knowledge['tech-stack']).toContain('mobile-app-deployment');
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
// Tests — Data-pipeline
|
|
749
|
+
// ---------------------------------------------------------------------------
|
|
750
|
+
describe('data-pipeline overlay integration', () => {
|
|
751
|
+
let tmpDir;
|
|
752
|
+
beforeEach(() => {
|
|
753
|
+
tmpDir = makeTempDir();
|
|
754
|
+
});
|
|
755
|
+
afterEach(() => {
|
|
756
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
757
|
+
vi.restoreAllMocks();
|
|
758
|
+
});
|
|
759
|
+
// Test 1: Config with data-pipeline + dataPipelineConfig validates through ConfigSchema
|
|
760
|
+
it('data-pipeline config with dataPipelineConfig validates through ConfigSchema', () => {
|
|
761
|
+
const result = ConfigSchema.safeParse({
|
|
762
|
+
version: 2,
|
|
763
|
+
methodology: 'deep',
|
|
764
|
+
platforms: ['claude-code'],
|
|
765
|
+
project: {
|
|
766
|
+
projectType: 'data-pipeline',
|
|
767
|
+
dataPipelineConfig: { processingModel: 'streaming' },
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
expect(result.success).toBe(true);
|
|
771
|
+
if (result.success) {
|
|
772
|
+
const project = result.data.project;
|
|
773
|
+
expect(project['projectType']).toBe('data-pipeline');
|
|
774
|
+
const dpc = project['dataPipelineConfig'];
|
|
775
|
+
expect(dpc['processingModel']).toBe('streaming');
|
|
776
|
+
expect(dpc['orchestration']).toBe('none'); // default
|
|
777
|
+
expect(dpc['dataQualityStrategy']).toBe('validation'); // default
|
|
778
|
+
expect(dpc['schemaManagement']).toBe('none'); // default
|
|
779
|
+
expect(dpc['hasDataCatalog']).toBe(false); // default
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
// Test 2: Init with projectType data-pipeline creates config with dataPipelineConfig
|
|
783
|
+
it('init with projectType data-pipeline creates config.yml with dataPipelineConfig defaults', async () => {
|
|
784
|
+
const output = createMockOutput();
|
|
785
|
+
const result = await runWizard({
|
|
786
|
+
projectRoot: tmpDir,
|
|
787
|
+
projectType: 'data-pipeline',
|
|
788
|
+
pipelineProcessing: 'batch',
|
|
789
|
+
methodology: 'deep',
|
|
790
|
+
force: false,
|
|
791
|
+
auto: true,
|
|
792
|
+
output,
|
|
793
|
+
});
|
|
794
|
+
expect(result.success).toBe(true);
|
|
795
|
+
const { config } = loadConfig(tmpDir, []);
|
|
796
|
+
expect(config).not.toBeNull();
|
|
797
|
+
expect(config.project?.projectType).toBe('data-pipeline');
|
|
798
|
+
expect(config.project?.dataPipelineConfig).toBeDefined();
|
|
799
|
+
expect(config.project?.dataPipelineConfig?.processingModel).toBe('batch');
|
|
800
|
+
});
|
|
801
|
+
// Test 3: config.yml round-trips through YAML correctly
|
|
802
|
+
it('config.yml round-trips projectType and dataPipelineConfig through YAML', async () => {
|
|
803
|
+
const output = createMockOutput();
|
|
804
|
+
await runWizard({
|
|
805
|
+
projectRoot: tmpDir,
|
|
806
|
+
projectType: 'data-pipeline',
|
|
807
|
+
pipelineProcessing: 'streaming',
|
|
808
|
+
methodology: 'deep',
|
|
809
|
+
force: false,
|
|
810
|
+
auto: true,
|
|
811
|
+
output,
|
|
812
|
+
});
|
|
813
|
+
const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
|
|
814
|
+
const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
815
|
+
const project = raw['project'];
|
|
816
|
+
expect(project['projectType']).toBe('data-pipeline');
|
|
817
|
+
expect(project['dataPipelineConfig']).toBeDefined();
|
|
818
|
+
const dpc = project['dataPipelineConfig'];
|
|
819
|
+
expect(dpc['processingModel']).toBe('streaming');
|
|
820
|
+
});
|
|
821
|
+
// Test 4: Overlay loads successfully from content/methodology
|
|
822
|
+
it('data-pipeline overlay loads without errors', () => {
|
|
823
|
+
const methodologyDir = getPackageMethodologyDir();
|
|
824
|
+
const overlayPath = path.join(methodologyDir, 'data-pipeline-overlay.yml');
|
|
825
|
+
const { overlay, errors } = loadOverlay(overlayPath);
|
|
826
|
+
expect(errors).toHaveLength(0);
|
|
827
|
+
expect(overlay).not.toBeNull();
|
|
828
|
+
expect(overlay.projectType).toBe('data-pipeline');
|
|
829
|
+
expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
|
|
830
|
+
});
|
|
831
|
+
// Test 5: Overlay injects data-pipeline knowledge into architecture step
|
|
832
|
+
it('overlay injects data-pipeline knowledge into system-architecture step', async () => {
|
|
833
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline');
|
|
834
|
+
expect(overlayState.knowledge['system-architecture']).toBeDefined();
|
|
835
|
+
expect(overlayState.knowledge['system-architecture']).toContain('data-pipeline-architecture');
|
|
836
|
+
expect(overlayState.knowledge['system-architecture']).toContain('data-pipeline-batch-patterns');
|
|
837
|
+
expect(overlayState.knowledge['system-architecture']).toContain('data-pipeline-streaming-patterns');
|
|
838
|
+
});
|
|
839
|
+
// Test 6: Overlay injects knowledge into tech-stack step
|
|
840
|
+
it('overlay injects data-pipeline knowledge into tech-stack step', async () => {
|
|
841
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline');
|
|
842
|
+
expect(overlayState.knowledge['tech-stack']).toBeDefined();
|
|
843
|
+
expect(overlayState.knowledge['tech-stack']).toContain('data-pipeline-architecture');
|
|
844
|
+
});
|
|
845
|
+
// Test 7: Overlay injects knowledge into testing steps
|
|
846
|
+
it('overlay injects data-pipeline knowledge into TDD, e2e, and create-evals steps', async () => {
|
|
847
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline');
|
|
848
|
+
expect(overlayState.knowledge['tdd']).toBeDefined();
|
|
849
|
+
expect(overlayState.knowledge['tdd']).toContain('data-pipeline-testing');
|
|
850
|
+
expect(overlayState.knowledge['tdd']).toContain('data-pipeline-quality');
|
|
851
|
+
expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
|
|
852
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('data-pipeline-testing');
|
|
853
|
+
expect(overlayState.knowledge['create-evals']).toBeDefined();
|
|
854
|
+
expect(overlayState.knowledge['create-evals']).toContain('data-pipeline-testing');
|
|
855
|
+
expect(overlayState.knowledge['create-evals']).toContain('data-pipeline-quality');
|
|
856
|
+
});
|
|
857
|
+
// Test 8: Overlay injects knowledge into foundational steps
|
|
858
|
+
it('overlay injects data-pipeline knowledge into foundational steps', async () => {
|
|
859
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline');
|
|
860
|
+
expect(overlayState.knowledge['create-prd']).toContain('data-pipeline-requirements');
|
|
861
|
+
expect(overlayState.knowledge['coding-standards']).toContain('data-pipeline-conventions');
|
|
862
|
+
expect(overlayState.knowledge['project-structure']).toContain('data-pipeline-project-structure');
|
|
863
|
+
});
|
|
864
|
+
// Test 9: Overlay injects knowledge into domain-modeling, security, and operations steps
|
|
865
|
+
it('overlay injects data-pipeline knowledge into domain-modeling, security, and operations', async () => {
|
|
866
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline');
|
|
867
|
+
expect(overlayState.knowledge['domain-modeling']).toBeDefined();
|
|
868
|
+
expect(overlayState.knowledge['domain-modeling']).toContain('data-pipeline-schema-management');
|
|
869
|
+
expect(overlayState.knowledge['security']).toBeDefined();
|
|
870
|
+
expect(overlayState.knowledge['security']).toContain('data-pipeline-security');
|
|
871
|
+
expect(overlayState.knowledge['operations']).toBeDefined();
|
|
872
|
+
expect(overlayState.knowledge['operations']).toContain('data-pipeline-orchestration');
|
|
873
|
+
});
|
|
874
|
+
// Test 10: MVP methodology with data-pipeline overlay works
|
|
875
|
+
it('MVP methodology with data-pipeline overlay injects knowledge', async () => {
|
|
876
|
+
const { overlayState } = await resolveProjectOverlay('data-pipeline', 'mvp');
|
|
877
|
+
expect(overlayState.knowledge['system-architecture']).toContain('data-pipeline-architecture');
|
|
878
|
+
expect(overlayState.knowledge['tdd']).toContain('data-pipeline-testing');
|
|
879
|
+
});
|
|
880
|
+
});
|
|
881
|
+
// ---------------------------------------------------------------------------
|
|
882
|
+
// Tests — ML
|
|
883
|
+
// ---------------------------------------------------------------------------
|
|
884
|
+
describe('ml overlay integration', () => {
|
|
885
|
+
let tmpDir;
|
|
886
|
+
beforeEach(() => {
|
|
887
|
+
tmpDir = makeTempDir();
|
|
888
|
+
});
|
|
889
|
+
afterEach(() => {
|
|
890
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
891
|
+
vi.restoreAllMocks();
|
|
892
|
+
});
|
|
893
|
+
// Test 1: Config with ml + mlConfig validates through ConfigSchema
|
|
894
|
+
it('ml config with mlConfig validates through ConfigSchema', () => {
|
|
895
|
+
const result = ConfigSchema.safeParse({
|
|
896
|
+
version: 2,
|
|
897
|
+
methodology: 'deep',
|
|
898
|
+
platforms: ['claude-code'],
|
|
899
|
+
project: {
|
|
900
|
+
projectType: 'ml',
|
|
901
|
+
mlConfig: { projectPhase: 'training' },
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
expect(result.success).toBe(true);
|
|
905
|
+
if (result.success) {
|
|
906
|
+
const project = result.data.project;
|
|
907
|
+
expect(project['projectType']).toBe('ml');
|
|
908
|
+
const mc = project['mlConfig'];
|
|
909
|
+
expect(mc['projectPhase']).toBe('training');
|
|
910
|
+
expect(mc['modelType']).toBe('deep-learning'); // default
|
|
911
|
+
expect(mc['servingPattern']).toBe('none'); // default
|
|
912
|
+
expect(mc['hasExperimentTracking']).toBe(true); // default
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
// Test 2: Init with projectType ml creates config with mlConfig
|
|
916
|
+
it('init with projectType ml creates config.yml with mlConfig defaults', async () => {
|
|
917
|
+
const output = createMockOutput();
|
|
918
|
+
const result = await runWizard({
|
|
919
|
+
projectRoot: tmpDir,
|
|
920
|
+
projectType: 'ml',
|
|
921
|
+
mlPhase: 'training',
|
|
922
|
+
methodology: 'deep',
|
|
923
|
+
force: false,
|
|
924
|
+
auto: true,
|
|
925
|
+
output,
|
|
926
|
+
});
|
|
927
|
+
expect(result.success).toBe(true);
|
|
928
|
+
const { config } = loadConfig(tmpDir, []);
|
|
929
|
+
expect(config).not.toBeNull();
|
|
930
|
+
expect(config.project?.projectType).toBe('ml');
|
|
931
|
+
expect(config.project?.mlConfig).toBeDefined();
|
|
932
|
+
expect(config.project?.mlConfig?.projectPhase).toBe('training');
|
|
933
|
+
});
|
|
934
|
+
// Test 3: config.yml round-trips through YAML correctly
|
|
935
|
+
it('config.yml round-trips projectType and mlConfig through YAML', async () => {
|
|
936
|
+
const output = createMockOutput();
|
|
937
|
+
await runWizard({
|
|
938
|
+
projectRoot: tmpDir,
|
|
939
|
+
projectType: 'ml',
|
|
940
|
+
mlPhase: 'inference',
|
|
941
|
+
mlServing: 'realtime',
|
|
942
|
+
methodology: 'deep',
|
|
943
|
+
force: false,
|
|
944
|
+
auto: true,
|
|
945
|
+
output,
|
|
946
|
+
});
|
|
947
|
+
const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
|
|
948
|
+
const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
949
|
+
const project = raw['project'];
|
|
950
|
+
expect(project['projectType']).toBe('ml');
|
|
951
|
+
expect(project['mlConfig']).toBeDefined();
|
|
952
|
+
const mc = project['mlConfig'];
|
|
953
|
+
expect(mc['projectPhase']).toBe('inference');
|
|
954
|
+
expect(mc['servingPattern']).toBe('realtime');
|
|
955
|
+
});
|
|
956
|
+
// Test 4: Overlay loads successfully from content/methodology
|
|
957
|
+
it('ml overlay loads without errors', () => {
|
|
958
|
+
const methodologyDir = getPackageMethodologyDir();
|
|
959
|
+
const overlayPath = path.join(methodologyDir, 'ml-overlay.yml');
|
|
960
|
+
const { overlay, errors } = loadOverlay(overlayPath);
|
|
961
|
+
expect(errors).toHaveLength(0);
|
|
962
|
+
expect(overlay).not.toBeNull();
|
|
963
|
+
expect(overlay.projectType).toBe('ml');
|
|
964
|
+
expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
|
|
965
|
+
});
|
|
966
|
+
// Test 5: Overlay injects ml knowledge into architecture step
|
|
967
|
+
it('overlay injects ml knowledge into system-architecture step', async () => {
|
|
968
|
+
const { overlayState } = await resolveProjectOverlay('ml');
|
|
969
|
+
expect(overlayState.knowledge['system-architecture']).toBeDefined();
|
|
970
|
+
expect(overlayState.knowledge['system-architecture']).toContain('ml-architecture');
|
|
971
|
+
expect(overlayState.knowledge['system-architecture']).toContain('ml-training-patterns');
|
|
972
|
+
expect(overlayState.knowledge['system-architecture']).toContain('ml-serving-patterns');
|
|
973
|
+
});
|
|
974
|
+
// Test 6: Overlay injects knowledge into tech-stack step
|
|
975
|
+
it('overlay injects ml knowledge into tech-stack step', async () => {
|
|
976
|
+
const { overlayState } = await resolveProjectOverlay('ml');
|
|
977
|
+
expect(overlayState.knowledge['tech-stack']).toBeDefined();
|
|
978
|
+
expect(overlayState.knowledge['tech-stack']).toContain('ml-architecture');
|
|
979
|
+
});
|
|
980
|
+
// Test 7: Overlay injects knowledge into testing steps
|
|
981
|
+
it('overlay injects ml knowledge into TDD, e2e, and create-evals steps', async () => {
|
|
982
|
+
const { overlayState } = await resolveProjectOverlay('ml');
|
|
983
|
+
expect(overlayState.knowledge['tdd']).toBeDefined();
|
|
984
|
+
expect(overlayState.knowledge['tdd']).toContain('ml-testing');
|
|
985
|
+
expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
|
|
986
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('ml-testing');
|
|
987
|
+
// model-evaluation routes to create-evals
|
|
988
|
+
expect(overlayState.knowledge['create-evals']).toBeDefined();
|
|
989
|
+
expect(overlayState.knowledge['create-evals']).toContain('ml-testing');
|
|
990
|
+
expect(overlayState.knowledge['create-evals']).toContain('ml-model-evaluation');
|
|
991
|
+
});
|
|
992
|
+
// Test 8: Overlay injects knowledge into foundational steps
|
|
993
|
+
it('overlay injects ml knowledge into foundational steps', async () => {
|
|
994
|
+
const { overlayState } = await resolveProjectOverlay('ml');
|
|
995
|
+
expect(overlayState.knowledge['create-prd']).toContain('ml-requirements');
|
|
996
|
+
expect(overlayState.knowledge['coding-standards']).toContain('ml-conventions');
|
|
997
|
+
expect(overlayState.knowledge['project-structure']).toContain('ml-project-structure');
|
|
998
|
+
});
|
|
999
|
+
// Test 9: Overlay injects knowledge into security and operations (experiment tracking + observability)
|
|
1000
|
+
it('overlay injects ml knowledge into security and operations steps', async () => {
|
|
1001
|
+
const { overlayState } = await resolveProjectOverlay('ml');
|
|
1002
|
+
expect(overlayState.knowledge['security']).toBeDefined();
|
|
1003
|
+
expect(overlayState.knowledge['security']).toContain('ml-security');
|
|
1004
|
+
expect(overlayState.knowledge['operations']).toBeDefined();
|
|
1005
|
+
expect(overlayState.knowledge['operations']).toContain('ml-experiment-tracking');
|
|
1006
|
+
expect(overlayState.knowledge['operations']).toContain('ml-observability');
|
|
1007
|
+
});
|
|
1008
|
+
// Test 10: MVP methodology with ml overlay works
|
|
1009
|
+
it('MVP methodology with ml overlay injects knowledge', async () => {
|
|
1010
|
+
const { overlayState } = await resolveProjectOverlay('ml', 'mvp');
|
|
1011
|
+
expect(overlayState.knowledge['system-architecture']).toContain('ml-architecture');
|
|
1012
|
+
expect(overlayState.knowledge['create-evals']).toContain('ml-model-evaluation');
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
// ---------------------------------------------------------------------------
|
|
1016
|
+
// Tests — Browser-extension
|
|
1017
|
+
// ---------------------------------------------------------------------------
|
|
1018
|
+
describe('browser-extension overlay integration', () => {
|
|
1019
|
+
let tmpDir;
|
|
1020
|
+
beforeEach(() => {
|
|
1021
|
+
tmpDir = makeTempDir();
|
|
1022
|
+
});
|
|
1023
|
+
afterEach(() => {
|
|
1024
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1025
|
+
vi.restoreAllMocks();
|
|
1026
|
+
});
|
|
1027
|
+
// Test 1: Config with browser-extension + browserExtensionConfig validates through ConfigSchema
|
|
1028
|
+
it('browser-extension config with browserExtensionConfig validates through ConfigSchema', () => {
|
|
1029
|
+
const result = ConfigSchema.safeParse({
|
|
1030
|
+
version: 2,
|
|
1031
|
+
methodology: 'deep',
|
|
1032
|
+
platforms: ['claude-code'],
|
|
1033
|
+
project: {
|
|
1034
|
+
projectType: 'browser-extension',
|
|
1035
|
+
browserExtensionConfig: { manifestVersion: '3' },
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
expect(result.success).toBe(true);
|
|
1039
|
+
if (result.success) {
|
|
1040
|
+
const project = result.data.project;
|
|
1041
|
+
expect(project['projectType']).toBe('browser-extension');
|
|
1042
|
+
const bec = project['browserExtensionConfig'];
|
|
1043
|
+
expect(bec['manifestVersion']).toBe('3');
|
|
1044
|
+
expect(bec['uiSurfaces']).toEqual(['popup']); // default
|
|
1045
|
+
expect(bec['hasContentScript']).toBe(false); // default
|
|
1046
|
+
expect(bec['hasBackgroundWorker']).toBe(true); // default
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
// Test 2: Init with projectType browser-extension creates config (no required flag in auto mode)
|
|
1050
|
+
it('init with projectType browser-extension creates config.yml with browserExtensionConfig defaults', async () => {
|
|
1051
|
+
const output = createMockOutput();
|
|
1052
|
+
const result = await runWizard({
|
|
1053
|
+
projectRoot: tmpDir,
|
|
1054
|
+
projectType: 'browser-extension',
|
|
1055
|
+
methodology: 'deep',
|
|
1056
|
+
force: false,
|
|
1057
|
+
auto: true,
|
|
1058
|
+
output,
|
|
1059
|
+
});
|
|
1060
|
+
expect(result.success).toBe(true);
|
|
1061
|
+
const { config } = loadConfig(tmpDir, []);
|
|
1062
|
+
expect(config).not.toBeNull();
|
|
1063
|
+
expect(config.project?.projectType).toBe('browser-extension');
|
|
1064
|
+
expect(config.project?.browserExtensionConfig).toBeDefined();
|
|
1065
|
+
expect(config.project?.browserExtensionConfig?.manifestVersion).toBe('3');
|
|
1066
|
+
expect(config.project?.browserExtensionConfig?.hasBackgroundWorker).toBe(true);
|
|
1067
|
+
});
|
|
1068
|
+
// Test 3: config.yml round-trips through YAML correctly
|
|
1069
|
+
it('config.yml round-trips projectType and browserExtensionConfig through YAML', async () => {
|
|
1070
|
+
const output = createMockOutput();
|
|
1071
|
+
await runWizard({
|
|
1072
|
+
projectRoot: tmpDir,
|
|
1073
|
+
projectType: 'browser-extension',
|
|
1074
|
+
extManifest: '3',
|
|
1075
|
+
extUiSurfaces: ['popup', 'sidepanel'],
|
|
1076
|
+
extContentScript: true,
|
|
1077
|
+
methodology: 'deep',
|
|
1078
|
+
force: false,
|
|
1079
|
+
auto: true,
|
|
1080
|
+
output,
|
|
1081
|
+
});
|
|
1082
|
+
const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
|
|
1083
|
+
const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
1084
|
+
const project = raw['project'];
|
|
1085
|
+
expect(project['projectType']).toBe('browser-extension');
|
|
1086
|
+
expect(project['browserExtensionConfig']).toBeDefined();
|
|
1087
|
+
const bec = project['browserExtensionConfig'];
|
|
1088
|
+
expect(bec['manifestVersion']).toBe('3');
|
|
1089
|
+
expect(bec['uiSurfaces']).toEqual(['popup', 'sidepanel']);
|
|
1090
|
+
expect(bec['hasContentScript']).toBe(true);
|
|
1091
|
+
});
|
|
1092
|
+
// Test 4: Overlay loads successfully from content/methodology
|
|
1093
|
+
it('browser-extension overlay loads without errors', () => {
|
|
1094
|
+
const methodologyDir = getPackageMethodologyDir();
|
|
1095
|
+
const overlayPath = path.join(methodologyDir, 'browser-extension-overlay.yml');
|
|
1096
|
+
const { overlay, errors } = loadOverlay(overlayPath);
|
|
1097
|
+
expect(errors).toHaveLength(0);
|
|
1098
|
+
expect(overlay).not.toBeNull();
|
|
1099
|
+
expect(overlay.projectType).toBe('browser-extension');
|
|
1100
|
+
expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
|
|
1101
|
+
});
|
|
1102
|
+
// Test 5: Overlay injects browser-extension knowledge into architecture step
|
|
1103
|
+
it('overlay injects browser-extension knowledge into system-architecture step', async () => {
|
|
1104
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension');
|
|
1105
|
+
expect(overlayState.knowledge['system-architecture']).toBeDefined();
|
|
1106
|
+
expect(overlayState.knowledge['system-architecture']).toContain('browser-extension-architecture');
|
|
1107
|
+
expect(overlayState.knowledge['system-architecture']).toContain('browser-extension-service-workers');
|
|
1108
|
+
});
|
|
1109
|
+
// Test 6: Overlay injects knowledge into tech-stack step
|
|
1110
|
+
it('overlay injects browser-extension knowledge into tech-stack step', async () => {
|
|
1111
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension');
|
|
1112
|
+
expect(overlayState.knowledge['tech-stack']).toBeDefined();
|
|
1113
|
+
expect(overlayState.knowledge['tech-stack']).toContain('browser-extension-architecture');
|
|
1114
|
+
expect(overlayState.knowledge['tech-stack']).toContain('browser-extension-manifest');
|
|
1115
|
+
});
|
|
1116
|
+
// Test 7: Overlay injects knowledge into testing steps
|
|
1117
|
+
it('overlay injects browser-extension knowledge into TDD and e2e steps (cross-browser)', async () => {
|
|
1118
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension');
|
|
1119
|
+
expect(overlayState.knowledge['tdd']).toBeDefined();
|
|
1120
|
+
expect(overlayState.knowledge['tdd']).toContain('browser-extension-testing');
|
|
1121
|
+
expect(overlayState.knowledge['tdd']).toContain('browser-extension-cross-browser');
|
|
1122
|
+
expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
|
|
1123
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('browser-extension-testing');
|
|
1124
|
+
expect(overlayState.knowledge['add-e2e-testing']).toContain('browser-extension-cross-browser');
|
|
1125
|
+
});
|
|
1126
|
+
// Test 8: Overlay injects knowledge into foundational steps (manifest in coding-standards)
|
|
1127
|
+
it('overlay injects browser-extension knowledge into foundational steps', async () => {
|
|
1128
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension');
|
|
1129
|
+
expect(overlayState.knowledge['create-prd']).toContain('browser-extension-requirements');
|
|
1130
|
+
expect(overlayState.knowledge['coding-standards']).toContain('browser-extension-conventions');
|
|
1131
|
+
expect(overlayState.knowledge['coding-standards']).toContain('browser-extension-manifest');
|
|
1132
|
+
expect(overlayState.knowledge['project-structure']).toContain('browser-extension-project-structure');
|
|
1133
|
+
});
|
|
1134
|
+
// Test 9: Overlay injects knowledge into security, ux-spec, and operations steps
|
|
1135
|
+
it('overlay injects browser-extension knowledge into security, ux-spec, and operations', async () => {
|
|
1136
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension');
|
|
1137
|
+
expect(overlayState.knowledge['security']).toBeDefined();
|
|
1138
|
+
expect(overlayState.knowledge['security']).toContain('browser-extension-security');
|
|
1139
|
+
expect(overlayState.knowledge['security']).toContain('browser-extension-content-scripts');
|
|
1140
|
+
expect(overlayState.knowledge['ux-spec']).toBeDefined();
|
|
1141
|
+
expect(overlayState.knowledge['ux-spec']).toContain('browser-extension-architecture');
|
|
1142
|
+
expect(overlayState.knowledge['operations']).toBeDefined();
|
|
1143
|
+
expect(overlayState.knowledge['operations']).toContain('browser-extension-store-submission');
|
|
1144
|
+
});
|
|
1145
|
+
// Test 10: MVP methodology with browser-extension overlay works
|
|
1146
|
+
it('MVP methodology with browser-extension overlay injects knowledge', async () => {
|
|
1147
|
+
const { overlayState } = await resolveProjectOverlay('browser-extension', 'mvp');
|
|
1148
|
+
expect(overlayState.knowledge['system-architecture']).toContain('browser-extension-architecture');
|
|
1149
|
+
expect(overlayState.knowledge['tdd']).toContain('browser-extension-cross-browser');
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
// ---------------------------------------------------------------------------
|
|
488
1153
|
// Cross-type validation tests
|
|
489
1154
|
// ---------------------------------------------------------------------------
|
|
490
1155
|
describe('project-type overlay cross-validation', () => {
|
|
491
1156
|
it('each overlay type injects distinct knowledge entries (no accidental overlap)', async () => {
|
|
492
|
-
const [webResult, backendResult, cliResult] = await Promise.all([
|
|
1157
|
+
const [webResult, backendResult, cliResult, libraryResult, mobileResult, pipelineResult, mlResult, extResult,] = await Promise.all([
|
|
493
1158
|
resolveProjectOverlay('web-app'),
|
|
494
1159
|
resolveProjectOverlay('backend'),
|
|
495
1160
|
resolveProjectOverlay('cli'),
|
|
1161
|
+
resolveProjectOverlay('library'),
|
|
1162
|
+
resolveProjectOverlay('mobile-app'),
|
|
1163
|
+
resolveProjectOverlay('data-pipeline'),
|
|
1164
|
+
resolveProjectOverlay('ml'),
|
|
1165
|
+
resolveProjectOverlay('browser-extension'),
|
|
496
1166
|
]);
|
|
497
1167
|
// system-architecture should have type-specific entries for each
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1168
|
+
const allArch = {
|
|
1169
|
+
'web-app': webResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1170
|
+
'backend': backendResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1171
|
+
'cli': cliResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1172
|
+
'library': libraryResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1173
|
+
'mobile-app': mobileResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1174
|
+
'data-pipeline': pipelineResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1175
|
+
'ml': mlResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1176
|
+
'browser-extension': extResult.overlayState.knowledge['system-architecture'] ?? [],
|
|
1177
|
+
};
|
|
1178
|
+
// Each project type's architecture step contains its own -architecture entry
|
|
1179
|
+
// and none of the other 7 type-specific architecture entries.
|
|
1180
|
+
const allTypes = Object.keys(allArch);
|
|
1181
|
+
for (const type of allTypes) {
|
|
1182
|
+
const ownEntry = `${type}-architecture`;
|
|
1183
|
+
expect(allArch[type], `${type} should contain ${ownEntry}`).toContain(ownEntry);
|
|
1184
|
+
for (const otherType of allTypes) {
|
|
1185
|
+
if (otherType === type)
|
|
1186
|
+
continue;
|
|
1187
|
+
const otherEntry = `${otherType}-architecture`;
|
|
1188
|
+
expect(allArch[type], `${type} system-architecture must not leak ${otherEntry}`).not.toContain(otherEntry);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
510
1191
|
});
|
|
511
1192
|
it('config schema rejects cross-typed config blocks', () => {
|
|
512
1193
|
// gameConfig on web-app
|
|
@@ -529,6 +1210,91 @@ describe('project-type overlay cross-validation', () => {
|
|
|
529
1210
|
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
530
1211
|
project: { projectType: 'game', cliConfig: { interactivity: 'hybrid' } },
|
|
531
1212
|
}).success).toBe(false);
|
|
1213
|
+
// libraryConfig on web-app
|
|
1214
|
+
expect(ConfigSchema.safeParse({
|
|
1215
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1216
|
+
project: { projectType: 'web-app', libraryConfig: { visibility: 'public' } },
|
|
1217
|
+
}).success).toBe(false);
|
|
1218
|
+
// mobileAppConfig on backend
|
|
1219
|
+
expect(ConfigSchema.safeParse({
|
|
1220
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1221
|
+
project: { projectType: 'backend', mobileAppConfig: { platform: 'ios' } },
|
|
1222
|
+
}).success).toBe(false);
|
|
1223
|
+
// libraryConfig on mobile-app
|
|
1224
|
+
expect(ConfigSchema.safeParse({
|
|
1225
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1226
|
+
project: { projectType: 'mobile-app', libraryConfig: { visibility: 'internal' } },
|
|
1227
|
+
}).success).toBe(false);
|
|
1228
|
+
// mobileAppConfig on library
|
|
1229
|
+
expect(ConfigSchema.safeParse({
|
|
1230
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1231
|
+
project: { projectType: 'library', mobileAppConfig: { platform: 'android' } },
|
|
1232
|
+
}).success).toBe(false);
|
|
1233
|
+
// dataPipelineConfig on backend
|
|
1234
|
+
expect(ConfigSchema.safeParse({
|
|
1235
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1236
|
+
project: { projectType: 'backend', dataPipelineConfig: { processingModel: 'streaming' } },
|
|
1237
|
+
}).success).toBe(false);
|
|
1238
|
+
// mlConfig on data-pipeline
|
|
1239
|
+
expect(ConfigSchema.safeParse({
|
|
1240
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1241
|
+
project: { projectType: 'data-pipeline', mlConfig: { projectPhase: 'training' } },
|
|
1242
|
+
}).success).toBe(false);
|
|
1243
|
+
// browserExtensionConfig on web-app
|
|
1244
|
+
expect(ConfigSchema.safeParse({
|
|
1245
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1246
|
+
project: { projectType: 'web-app', browserExtensionConfig: { manifestVersion: '3' } },
|
|
1247
|
+
}).success).toBe(false);
|
|
1248
|
+
// dataPipelineConfig on ml
|
|
1249
|
+
expect(ConfigSchema.safeParse({
|
|
1250
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1251
|
+
project: { projectType: 'ml', dataPipelineConfig: { processingModel: 'batch' } },
|
|
1252
|
+
}).success).toBe(false);
|
|
1253
|
+
});
|
|
1254
|
+
it('ml inference projects must specify a serving pattern', () => {
|
|
1255
|
+
// inference + none → invalid
|
|
1256
|
+
expect(ConfigSchema.safeParse({
|
|
1257
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1258
|
+
project: { projectType: 'ml', mlConfig: { projectPhase: 'inference', servingPattern: 'none' } },
|
|
1259
|
+
}).success).toBe(false);
|
|
1260
|
+
// inference + realtime → valid
|
|
1261
|
+
expect(ConfigSchema.safeParse({
|
|
1262
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1263
|
+
project: { projectType: 'ml', mlConfig: { projectPhase: 'inference', servingPattern: 'realtime' } },
|
|
1264
|
+
}).success).toBe(true);
|
|
1265
|
+
// training + serving pattern → invalid
|
|
1266
|
+
expect(ConfigSchema.safeParse({
|
|
1267
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1268
|
+
project: { projectType: 'ml', mlConfig: { projectPhase: 'training', servingPattern: 'realtime' } },
|
|
1269
|
+
}).success).toBe(false);
|
|
1270
|
+
});
|
|
1271
|
+
it('browser-extension must have at least one capability', () => {
|
|
1272
|
+
// No surfaces, no content script, no background worker → invalid
|
|
1273
|
+
expect(ConfigSchema.safeParse({
|
|
1274
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1275
|
+
project: {
|
|
1276
|
+
projectType: 'browser-extension',
|
|
1277
|
+
browserExtensionConfig: {
|
|
1278
|
+
manifestVersion: '3',
|
|
1279
|
+
uiSurfaces: [],
|
|
1280
|
+
hasContentScript: false,
|
|
1281
|
+
hasBackgroundWorker: false,
|
|
1282
|
+
},
|
|
1283
|
+
},
|
|
1284
|
+
}).success).toBe(false);
|
|
1285
|
+
// Just a content script → valid
|
|
1286
|
+
expect(ConfigSchema.safeParse({
|
|
1287
|
+
version: 2, methodology: 'deep', platforms: ['claude-code'],
|
|
1288
|
+
project: {
|
|
1289
|
+
projectType: 'browser-extension',
|
|
1290
|
+
browserExtensionConfig: {
|
|
1291
|
+
manifestVersion: '3',
|
|
1292
|
+
uiSurfaces: [],
|
|
1293
|
+
hasContentScript: true,
|
|
1294
|
+
hasBackgroundWorker: false,
|
|
1295
|
+
},
|
|
1296
|
+
},
|
|
1297
|
+
}).success).toBe(true);
|
|
532
1298
|
});
|
|
533
1299
|
});
|
|
534
1300
|
//# sourceMappingURL=project-type-overlays.test.js.map
|