@zigrivers/scaffold 3.6.0 → 3.8.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.
Files changed (115) hide show
  1. package/README.md +127 -12
  2. package/content/knowledge/backend/backend-api-design.md +103 -0
  3. package/content/knowledge/backend/backend-architecture.md +100 -0
  4. package/content/knowledge/backend/backend-async-patterns.md +101 -0
  5. package/content/knowledge/backend/backend-auth-patterns.md +100 -0
  6. package/content/knowledge/backend/backend-conventions.md +105 -0
  7. package/content/knowledge/backend/backend-data-modeling.md +102 -0
  8. package/content/knowledge/backend/backend-deployment.md +100 -0
  9. package/content/knowledge/backend/backend-dev-environment.md +102 -0
  10. package/content/knowledge/backend/backend-observability.md +102 -0
  11. package/content/knowledge/backend/backend-project-structure.md +100 -0
  12. package/content/knowledge/backend/backend-requirements.md +103 -0
  13. package/content/knowledge/backend/backend-security.md +104 -0
  14. package/content/knowledge/backend/backend-testing.md +101 -0
  15. package/content/knowledge/backend/backend-worker-patterns.md +100 -0
  16. package/content/knowledge/cli/cli-architecture.md +101 -0
  17. package/content/knowledge/cli/cli-conventions.md +117 -0
  18. package/content/knowledge/cli/cli-dev-environment.md +121 -0
  19. package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
  20. package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
  21. package/content/knowledge/cli/cli-output-patterns.md +107 -0
  22. package/content/knowledge/cli/cli-project-structure.md +124 -0
  23. package/content/knowledge/cli/cli-requirements.md +101 -0
  24. package/content/knowledge/cli/cli-shell-integration.md +130 -0
  25. package/content/knowledge/cli/cli-testing.md +134 -0
  26. package/content/knowledge/library/library-api-design.md +306 -0
  27. package/content/knowledge/library/library-architecture.md +247 -0
  28. package/content/knowledge/library/library-bundling.md +244 -0
  29. package/content/knowledge/library/library-conventions.md +229 -0
  30. package/content/knowledge/library/library-dev-environment.md +220 -0
  31. package/content/knowledge/library/library-documentation.md +300 -0
  32. package/content/knowledge/library/library-project-structure.md +237 -0
  33. package/content/knowledge/library/library-requirements.md +173 -0
  34. package/content/knowledge/library/library-security.md +257 -0
  35. package/content/knowledge/library/library-testing.md +319 -0
  36. package/content/knowledge/library/library-type-definitions.md +284 -0
  37. package/content/knowledge/library/library-versioning.md +300 -0
  38. package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
  39. package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
  40. package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
  41. package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
  42. package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
  43. package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
  44. package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
  45. package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
  46. package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
  47. package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
  48. package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
  49. package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
  50. package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
  51. package/content/knowledge/web-app/web-app-architecture.md +116 -0
  52. package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
  53. package/content/knowledge/web-app/web-app-conventions.md +121 -0
  54. package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
  55. package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
  56. package/content/knowledge/web-app/web-app-deployment.md +134 -0
  57. package/content/knowledge/web-app/web-app-design-system.md +158 -0
  58. package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
  59. package/content/knowledge/web-app/web-app-observability.md +221 -0
  60. package/content/knowledge/web-app/web-app-project-structure.md +160 -0
  61. package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
  62. package/content/knowledge/web-app/web-app-requirements.md +112 -0
  63. package/content/knowledge/web-app/web-app-security.md +193 -0
  64. package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
  65. package/content/knowledge/web-app/web-app-testing.md +249 -0
  66. package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
  67. package/content/methodology/backend-overlay.yml +73 -0
  68. package/content/methodology/cli-overlay.yml +69 -0
  69. package/content/methodology/library-overlay.yml +67 -0
  70. package/content/methodology/mobile-app-overlay.yml +71 -0
  71. package/content/methodology/web-app-overlay.yml +79 -0
  72. package/dist/cli/commands/init.d.ts +21 -0
  73. package/dist/cli/commands/init.d.ts.map +1 -1
  74. package/dist/cli/commands/init.js +261 -13
  75. package/dist/cli/commands/init.js.map +1 -1
  76. package/dist/cli/commands/init.test.js +206 -0
  77. package/dist/cli/commands/init.test.js.map +1 -1
  78. package/dist/config/schema.d.ts +1392 -64
  79. package/dist/config/schema.d.ts.map +1 -1
  80. package/dist/config/schema.js +82 -5
  81. package/dist/config/schema.js.map +1 -1
  82. package/dist/config/schema.test.js +302 -1
  83. package/dist/config/schema.test.js.map +1 -1
  84. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  85. package/dist/core/assembly/overlay-loader.js +2 -1
  86. package/dist/core/assembly/overlay-loader.js.map +1 -1
  87. package/dist/core/assembly/overlay-loader.test.js +56 -0
  88. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  89. package/dist/e2e/game-pipeline.test.js +1 -0
  90. package/dist/e2e/game-pipeline.test.js.map +1 -1
  91. package/dist/e2e/project-type-overlays.test.d.ts +16 -0
  92. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
  93. package/dist/e2e/project-type-overlays.test.js +834 -0
  94. package/dist/e2e/project-type-overlays.test.js.map +1 -0
  95. package/dist/types/config.d.ts +19 -2
  96. package/dist/types/config.d.ts.map +1 -1
  97. package/dist/types/index.d.ts +0 -1
  98. package/dist/types/index.d.ts.map +1 -1
  99. package/dist/types/index.js +0 -1
  100. package/dist/types/index.js.map +1 -1
  101. package/dist/wizard/questions.d.ts +27 -1
  102. package/dist/wizard/questions.d.ts.map +1 -1
  103. package/dist/wizard/questions.js +142 -3
  104. package/dist/wizard/questions.js.map +1 -1
  105. package/dist/wizard/questions.test.js +206 -8
  106. package/dist/wizard/questions.test.js.map +1 -1
  107. package/dist/wizard/wizard.d.ts +21 -0
  108. package/dist/wizard/wizard.d.ts.map +1 -1
  109. package/dist/wizard/wizard.js +27 -1
  110. package/dist/wizard/wizard.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/types/wizard.d.ts +0 -14
  113. package/dist/types/wizard.d.ts.map +0 -1
  114. package/dist/types/wizard.js +0 -2
  115. package/dist/types/wizard.js.map +0 -1
@@ -0,0 +1,834 @@
1
+ /**
2
+ * E2E integration tests for project-type overlay flow:
3
+ * init → config.yml → overlay resolution → knowledge injection
4
+ *
5
+ * Tests the full pipeline for web-app, backend, cli, library, and mobile-app
6
+ * project types:
7
+ * 1. Init creates config with project-type-specific config block
8
+ * 2. Config validates through ConfigSchema
9
+ * 3. Overlay loads and resolves against real pipeline meta-prompts
10
+ * 4. Knowledge entries are injected into the correct steps
11
+ *
12
+ * Follows the same pattern as game-pipeline.test.ts: real temp dirs, mocked
13
+ * detectProjectMode and discoverMetaPrompts.
14
+ */
15
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import os from 'node:os';
19
+ import yaml from 'js-yaml';
20
+ // ---------------------------------------------------------------------------
21
+ // Hoisted mocks — must appear before real imports
22
+ // ---------------------------------------------------------------------------
23
+ vi.mock('../../src/project/detector.js', () => ({
24
+ detectProjectMode: vi.fn(() => ({
25
+ mode: 'greenfield',
26
+ signals: [],
27
+ methodologySuggestion: 'deep',
28
+ sourceFileCount: 0,
29
+ })),
30
+ }));
31
+ vi.mock('../../src/core/assembly/meta-prompt-loader.js', () => ({
32
+ discoverMetaPrompts: vi.fn(() => new Map()),
33
+ discoverAllMetaPrompts: vi.fn(() => new Map()),
34
+ }));
35
+ // ---------------------------------------------------------------------------
36
+ // Real imports (after mock declarations)
37
+ // ---------------------------------------------------------------------------
38
+ import { runWizard } from '../wizard/wizard.js';
39
+ import { loadConfig } from '../config/loader.js';
40
+ import { ConfigSchema } from '../config/schema.js';
41
+ import { loadAllPresets } from '../core/assembly/preset-loader.js';
42
+ import { resolveOverlayState } from '../core/assembly/overlay-state-resolver.js';
43
+ import { loadOverlay } from '../core/assembly/overlay-loader.js';
44
+ import { getPackagePipelineDir, getPackageMethodologyDir } from '../utils/fs.js';
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers
47
+ // ---------------------------------------------------------------------------
48
+ function makeTempDir() {
49
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'scaffold-e2e-overlay-'));
50
+ }
51
+ function createMockOutput() {
52
+ return {
53
+ success: vi.fn(),
54
+ info: vi.fn(),
55
+ warn: vi.fn(),
56
+ error: vi.fn(),
57
+ result: vi.fn(),
58
+ prompt: vi.fn().mockResolvedValue(''),
59
+ confirm: vi.fn().mockResolvedValue(false),
60
+ select: vi.fn().mockResolvedValue(''),
61
+ multiSelect: vi.fn().mockResolvedValue([]),
62
+ multiInput: vi.fn().mockResolvedValue([]),
63
+ startSpinner: vi.fn(),
64
+ stopSpinner: vi.fn(),
65
+ startProgress: vi.fn(),
66
+ updateProgress: vi.fn(),
67
+ stopProgress: vi.fn(),
68
+ };
69
+ }
70
+ /**
71
+ * Discover real meta-prompts from the package pipeline directory.
72
+ * Uses vi.importActual to bypass the vi.mock and get the real loader.
73
+ */
74
+ async function discoverRealMetaPrompts() {
75
+ const pipelineDir = getPackagePipelineDir();
76
+ const actual = await vi.importActual('../core/assembly/meta-prompt-loader.js');
77
+ return actual.discoverMetaPrompts(pipelineDir);
78
+ }
79
+ /**
80
+ * Resolve an overlay with a given project type and methodology against the real pipeline.
81
+ */
82
+ async function resolveProjectOverlay(projectType, methodology = 'deep') {
83
+ const methodologyDir = getPackageMethodologyDir();
84
+ const realMetaPrompts = await discoverRealMetaPrompts();
85
+ const knownSteps = [...realMetaPrompts.keys()];
86
+ const presets = loadAllPresets(methodologyDir, knownSteps);
87
+ const output = createMockOutput();
88
+ const preset = methodology === 'mvp' ? presets.mvp : presets.deep;
89
+ const overlayState = resolveOverlayState({
90
+ config: {
91
+ version: 2,
92
+ methodology,
93
+ platforms: ['claude-code'],
94
+ project: { projectType },
95
+ },
96
+ methodologyDir,
97
+ metaPrompts: realMetaPrompts,
98
+ presetSteps: preset?.steps ?? {},
99
+ output,
100
+ });
101
+ return { overlayState, realMetaPrompts };
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Tests — Web-app
105
+ // ---------------------------------------------------------------------------
106
+ describe('web-app overlay integration', () => {
107
+ let tmpDir;
108
+ beforeEach(() => {
109
+ tmpDir = makeTempDir();
110
+ });
111
+ afterEach(() => {
112
+ fs.rmSync(tmpDir, { recursive: true, force: true });
113
+ vi.restoreAllMocks();
114
+ });
115
+ // Test 1: Config with web-app + webAppConfig validates through ConfigSchema
116
+ it('web-app config with webAppConfig validates through ConfigSchema', () => {
117
+ const result = ConfigSchema.safeParse({
118
+ version: 2,
119
+ methodology: 'deep',
120
+ platforms: ['claude-code'],
121
+ project: {
122
+ projectType: 'web-app',
123
+ webAppConfig: { renderingStrategy: 'ssr' },
124
+ },
125
+ });
126
+ expect(result.success).toBe(true);
127
+ if (result.success) {
128
+ const project = result.data.project;
129
+ expect(project['projectType']).toBe('web-app');
130
+ const wac = project['webAppConfig'];
131
+ expect(wac['renderingStrategy']).toBe('ssr');
132
+ expect(wac['deployTarget']).toBe('serverless'); // default
133
+ }
134
+ });
135
+ // Test 2: Init with projectType web-app creates config with webAppConfig
136
+ it('init with projectType web-app creates config.yml with webAppConfig defaults', async () => {
137
+ const output = createMockOutput();
138
+ const result = await runWizard({
139
+ projectRoot: tmpDir,
140
+ projectType: 'web-app',
141
+ webRendering: 'ssr',
142
+ methodology: 'deep',
143
+ force: false,
144
+ auto: true,
145
+ output,
146
+ });
147
+ expect(result.success).toBe(true);
148
+ const { config } = loadConfig(tmpDir, []);
149
+ expect(config).not.toBeNull();
150
+ expect(config.methodology).toBe('deep');
151
+ expect(config.project?.projectType).toBe('web-app');
152
+ expect(config.project?.webAppConfig).toBeDefined();
153
+ expect(config.project?.webAppConfig?.renderingStrategy).toBe('ssr');
154
+ });
155
+ // Test 3: config.yml round-trips through YAML correctly
156
+ it('config.yml round-trips projectType and webAppConfig through YAML', async () => {
157
+ const output = createMockOutput();
158
+ await runWizard({
159
+ projectRoot: tmpDir,
160
+ projectType: 'web-app',
161
+ webRendering: 'spa',
162
+ methodology: 'deep',
163
+ force: false,
164
+ auto: true,
165
+ output,
166
+ });
167
+ const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
168
+ const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
169
+ const project = raw['project'];
170
+ expect(project['projectType']).toBe('web-app');
171
+ expect(project['webAppConfig']).toBeDefined();
172
+ const wac = project['webAppConfig'];
173
+ expect(wac['renderingStrategy']).toBe('spa');
174
+ });
175
+ // Test 4: Overlay loads successfully from content/methodology
176
+ it('web-app overlay loads without errors', () => {
177
+ const methodologyDir = getPackageMethodologyDir();
178
+ const overlayPath = path.join(methodologyDir, 'web-app-overlay.yml');
179
+ const { overlay, errors } = loadOverlay(overlayPath);
180
+ expect(errors).toHaveLength(0);
181
+ expect(overlay).not.toBeNull();
182
+ expect(overlay.projectType).toBe('web-app');
183
+ expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
184
+ });
185
+ // Test 5: Overlay injects web-app knowledge into architecture step
186
+ it('overlay injects web-app-architecture into system-architecture step', async () => {
187
+ const { overlayState } = await resolveProjectOverlay('web-app');
188
+ expect(overlayState.knowledge['system-architecture']).toBeDefined();
189
+ expect(overlayState.knowledge['system-architecture']).toContain('web-app-architecture');
190
+ expect(overlayState.knowledge['system-architecture']).toContain('web-app-deployment');
191
+ });
192
+ // Test 6: Overlay injects knowledge into tech-stack step
193
+ it('overlay injects web-app knowledge into tech-stack step', async () => {
194
+ const { overlayState } = await resolveProjectOverlay('web-app');
195
+ expect(overlayState.knowledge['tech-stack']).toBeDefined();
196
+ expect(overlayState.knowledge['tech-stack']).toContain('web-app-rendering-strategies');
197
+ expect(overlayState.knowledge['tech-stack']).toContain('web-app-deployment');
198
+ expect(overlayState.knowledge['tech-stack']).toContain('web-app-auth-patterns');
199
+ });
200
+ // Test 7: Overlay injects knowledge into testing steps
201
+ it('overlay injects web-app-testing into TDD and e2e steps', async () => {
202
+ const { overlayState } = await resolveProjectOverlay('web-app');
203
+ expect(overlayState.knowledge['tdd']).toBeDefined();
204
+ expect(overlayState.knowledge['tdd']).toContain('web-app-testing');
205
+ expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
206
+ expect(overlayState.knowledge['add-e2e-testing']).toContain('web-app-testing');
207
+ });
208
+ // Test 8: Overlay injects knowledge into foundational steps
209
+ it('overlay injects web-app knowledge into foundational steps', async () => {
210
+ const { overlayState } = await resolveProjectOverlay('web-app');
211
+ expect(overlayState.knowledge['create-prd']).toContain('web-app-requirements');
212
+ expect(overlayState.knowledge['coding-standards']).toContain('web-app-conventions');
213
+ expect(overlayState.knowledge['project-structure']).toContain('web-app-project-structure');
214
+ });
215
+ // Test 9: No step overrides (web-app overlay is knowledge-only)
216
+ it('web-app overlay does not override step enablement', async () => {
217
+ const { overlayState } = await resolveProjectOverlay('web-app');
218
+ // web-app overlay has no step-overrides section
219
+ // All steps should match the deep preset exactly
220
+ const methodologyDir = getPackageMethodologyDir();
221
+ const realMetaPrompts = await discoverRealMetaPrompts();
222
+ const knownSteps = [...realMetaPrompts.keys()];
223
+ const presets = loadAllPresets(methodologyDir, knownSteps);
224
+ const deepSteps = presets.deep?.steps ?? {};
225
+ for (const [stepName, entry] of Object.entries(deepSteps)) {
226
+ expect(overlayState.steps[stepName]?.enabled, `${stepName} enablement should match preset`).toBe(entry.enabled);
227
+ }
228
+ });
229
+ // Test 10: MVP methodology with web-app overlay works
230
+ it('MVP methodology with web-app overlay injects knowledge', async () => {
231
+ const { overlayState } = await resolveProjectOverlay('web-app', 'mvp');
232
+ expect(overlayState.knowledge['system-architecture']).toContain('web-app-architecture');
233
+ expect(overlayState.knowledge['tech-stack']).toContain('web-app-rendering-strategies');
234
+ });
235
+ });
236
+ // ---------------------------------------------------------------------------
237
+ // Tests — Backend
238
+ // ---------------------------------------------------------------------------
239
+ describe('backend overlay integration', () => {
240
+ let tmpDir;
241
+ beforeEach(() => {
242
+ tmpDir = makeTempDir();
243
+ });
244
+ afterEach(() => {
245
+ fs.rmSync(tmpDir, { recursive: true, force: true });
246
+ vi.restoreAllMocks();
247
+ });
248
+ // Test 1: Config with backend + backendConfig validates through ConfigSchema
249
+ it('backend config with backendConfig validates through ConfigSchema', () => {
250
+ const result = ConfigSchema.safeParse({
251
+ version: 2,
252
+ methodology: 'deep',
253
+ platforms: ['claude-code'],
254
+ project: {
255
+ projectType: 'backend',
256
+ backendConfig: { apiStyle: 'rest' },
257
+ },
258
+ });
259
+ expect(result.success).toBe(true);
260
+ if (result.success) {
261
+ const project = result.data.project;
262
+ expect(project['projectType']).toBe('backend');
263
+ const bc = project['backendConfig'];
264
+ expect(bc['apiStyle']).toBe('rest');
265
+ expect(bc['dataStore']).toEqual(['relational']); // default
266
+ expect(bc['deployTarget']).toBe('container'); // default
267
+ }
268
+ });
269
+ // Test 2: Init with projectType backend creates config with backendConfig
270
+ it('init with projectType backend creates config.yml with backendConfig defaults', async () => {
271
+ const output = createMockOutput();
272
+ const result = await runWizard({
273
+ projectRoot: tmpDir,
274
+ projectType: 'backend',
275
+ backendApiStyle: 'rest',
276
+ methodology: 'deep',
277
+ force: false,
278
+ auto: true,
279
+ output,
280
+ });
281
+ expect(result.success).toBe(true);
282
+ const { config } = loadConfig(tmpDir, []);
283
+ expect(config).not.toBeNull();
284
+ expect(config.project?.projectType).toBe('backend');
285
+ expect(config.project?.backendConfig).toBeDefined();
286
+ expect(config.project?.backendConfig?.apiStyle).toBe('rest');
287
+ });
288
+ // Test 3: config.yml round-trips through YAML correctly
289
+ it('config.yml round-trips projectType and backendConfig through YAML', async () => {
290
+ const output = createMockOutput();
291
+ await runWizard({
292
+ projectRoot: tmpDir,
293
+ projectType: 'backend',
294
+ backendApiStyle: 'graphql',
295
+ methodology: 'deep',
296
+ force: false,
297
+ auto: true,
298
+ output,
299
+ });
300
+ const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
301
+ const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
302
+ const project = raw['project'];
303
+ expect(project['projectType']).toBe('backend');
304
+ expect(project['backendConfig']).toBeDefined();
305
+ const bc = project['backendConfig'];
306
+ expect(bc['apiStyle']).toBe('graphql');
307
+ });
308
+ // Test 4: Overlay loads successfully from content/methodology
309
+ it('backend overlay loads without errors', () => {
310
+ const methodologyDir = getPackageMethodologyDir();
311
+ const overlayPath = path.join(methodologyDir, 'backend-overlay.yml');
312
+ const { overlay, errors } = loadOverlay(overlayPath);
313
+ expect(errors).toHaveLength(0);
314
+ expect(overlay).not.toBeNull();
315
+ expect(overlay.projectType).toBe('backend');
316
+ expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
317
+ });
318
+ // Test 5: Overlay injects backend knowledge into architecture step
319
+ it('overlay injects backend-architecture into system-architecture step', async () => {
320
+ const { overlayState } = await resolveProjectOverlay('backend');
321
+ expect(overlayState.knowledge['system-architecture']).toBeDefined();
322
+ expect(overlayState.knowledge['system-architecture']).toContain('backend-architecture');
323
+ expect(overlayState.knowledge['system-architecture']).toContain('backend-async-patterns');
324
+ });
325
+ // Test 6: Overlay injects knowledge into tech-stack step
326
+ it('overlay injects backend knowledge into tech-stack step', async () => {
327
+ const { overlayState } = await resolveProjectOverlay('backend');
328
+ expect(overlayState.knowledge['tech-stack']).toBeDefined();
329
+ expect(overlayState.knowledge['tech-stack']).toContain('backend-architecture');
330
+ expect(overlayState.knowledge['tech-stack']).toContain('backend-api-design');
331
+ expect(overlayState.knowledge['tech-stack']).toContain('backend-auth-patterns');
332
+ });
333
+ // Test 7: Overlay injects knowledge into testing steps
334
+ it('overlay injects backend-testing into TDD and e2e steps', async () => {
335
+ const { overlayState } = await resolveProjectOverlay('backend');
336
+ expect(overlayState.knowledge['tdd']).toBeDefined();
337
+ expect(overlayState.knowledge['tdd']).toContain('backend-testing');
338
+ expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
339
+ expect(overlayState.knowledge['add-e2e-testing']).toContain('backend-testing');
340
+ });
341
+ // Test 8: Overlay injects knowledge into foundational steps
342
+ it('overlay injects backend knowledge into foundational steps', async () => {
343
+ const { overlayState } = await resolveProjectOverlay('backend');
344
+ expect(overlayState.knowledge['create-prd']).toContain('backend-requirements');
345
+ expect(overlayState.knowledge['coding-standards']).toContain('backend-conventions');
346
+ expect(overlayState.knowledge['project-structure']).toContain('backend-project-structure');
347
+ });
348
+ // Test 9: Overlay injects knowledge into operations step
349
+ it('overlay injects backend knowledge into operations step', async () => {
350
+ const { overlayState } = await resolveProjectOverlay('backend');
351
+ expect(overlayState.knowledge['operations']).toBeDefined();
352
+ expect(overlayState.knowledge['operations']).toContain('backend-deployment');
353
+ expect(overlayState.knowledge['operations']).toContain('backend-observability');
354
+ expect(overlayState.knowledge['operations']).toContain('backend-async-patterns');
355
+ });
356
+ // Test 10: MVP methodology with backend overlay works
357
+ it('MVP methodology with backend overlay injects knowledge', async () => {
358
+ const { overlayState } = await resolveProjectOverlay('backend', 'mvp');
359
+ expect(overlayState.knowledge['system-architecture']).toContain('backend-architecture');
360
+ expect(overlayState.knowledge['tech-stack']).toContain('backend-api-design');
361
+ });
362
+ });
363
+ // ---------------------------------------------------------------------------
364
+ // Tests — CLI
365
+ // ---------------------------------------------------------------------------
366
+ describe('cli overlay integration', () => {
367
+ let tmpDir;
368
+ beforeEach(() => {
369
+ tmpDir = makeTempDir();
370
+ });
371
+ afterEach(() => {
372
+ fs.rmSync(tmpDir, { recursive: true, force: true });
373
+ vi.restoreAllMocks();
374
+ });
375
+ // Test 1: Config with cli + cliConfig validates through ConfigSchema
376
+ it('cli config with cliConfig validates through ConfigSchema', () => {
377
+ const result = ConfigSchema.safeParse({
378
+ version: 2,
379
+ methodology: 'deep',
380
+ platforms: ['claude-code'],
381
+ project: {
382
+ projectType: 'cli',
383
+ cliConfig: { interactivity: 'hybrid' },
384
+ },
385
+ });
386
+ expect(result.success).toBe(true);
387
+ if (result.success) {
388
+ const project = result.data.project;
389
+ expect(project['projectType']).toBe('cli');
390
+ const cc = project['cliConfig'];
391
+ expect(cc['interactivity']).toBe('hybrid');
392
+ expect(cc['distributionChannels']).toEqual(['package-manager']); // default
393
+ expect(cc['hasStructuredOutput']).toBe(false); // default
394
+ }
395
+ });
396
+ // Test 2: Init with projectType cli creates config with cliConfig
397
+ it('init with projectType cli creates config.yml with cliConfig defaults', async () => {
398
+ const output = createMockOutput();
399
+ const result = await runWizard({
400
+ projectRoot: tmpDir,
401
+ projectType: 'cli',
402
+ cliInteractivity: 'hybrid',
403
+ methodology: 'deep',
404
+ force: false,
405
+ auto: true,
406
+ output,
407
+ });
408
+ expect(result.success).toBe(true);
409
+ const { config } = loadConfig(tmpDir, []);
410
+ expect(config).not.toBeNull();
411
+ expect(config.project?.projectType).toBe('cli');
412
+ expect(config.project?.cliConfig).toBeDefined();
413
+ expect(config.project?.cliConfig?.interactivity).toBe('hybrid');
414
+ });
415
+ // Test 3: config.yml round-trips through YAML correctly
416
+ it('config.yml round-trips projectType and cliConfig through YAML', async () => {
417
+ const output = createMockOutput();
418
+ await runWizard({
419
+ projectRoot: tmpDir,
420
+ projectType: 'cli',
421
+ cliInteractivity: 'args-only',
422
+ methodology: 'deep',
423
+ force: false,
424
+ auto: true,
425
+ output,
426
+ });
427
+ const configPath = path.join(tmpDir, '.scaffold', 'config.yml');
428
+ const raw = yaml.load(fs.readFileSync(configPath, 'utf8'));
429
+ const project = raw['project'];
430
+ expect(project['projectType']).toBe('cli');
431
+ expect(project['cliConfig']).toBeDefined();
432
+ const cc = project['cliConfig'];
433
+ expect(cc['interactivity']).toBe('args-only');
434
+ });
435
+ // Test 4: Overlay loads successfully from content/methodology
436
+ it('cli overlay loads without errors', () => {
437
+ const methodologyDir = getPackageMethodologyDir();
438
+ const overlayPath = path.join(methodologyDir, 'cli-overlay.yml');
439
+ const { overlay, errors } = loadOverlay(overlayPath);
440
+ expect(errors).toHaveLength(0);
441
+ expect(overlay).not.toBeNull();
442
+ expect(overlay.projectType).toBe('cli');
443
+ expect(Object.keys(overlay.knowledgeOverrides).length).toBeGreaterThan(0);
444
+ });
445
+ // Test 5: Overlay injects cli knowledge into architecture step
446
+ it('overlay injects cli-architecture into system-architecture step', async () => {
447
+ const { overlayState } = await resolveProjectOverlay('cli');
448
+ expect(overlayState.knowledge['system-architecture']).toBeDefined();
449
+ expect(overlayState.knowledge['system-architecture']).toContain('cli-architecture');
450
+ expect(overlayState.knowledge['system-architecture']).toContain('cli-interactivity-patterns');
451
+ });
452
+ // Test 6: Overlay injects knowledge into tech-stack step
453
+ it('overlay injects cli knowledge into tech-stack step', async () => {
454
+ const { overlayState } = await resolveProjectOverlay('cli');
455
+ expect(overlayState.knowledge['tech-stack']).toBeDefined();
456
+ expect(overlayState.knowledge['tech-stack']).toContain('cli-architecture');
457
+ expect(overlayState.knowledge['tech-stack']).toContain('cli-distribution-patterns');
458
+ });
459
+ // Test 7: Overlay injects knowledge into testing steps
460
+ it('overlay injects cli-testing into TDD and e2e steps', async () => {
461
+ const { overlayState } = await resolveProjectOverlay('cli');
462
+ expect(overlayState.knowledge['tdd']).toBeDefined();
463
+ expect(overlayState.knowledge['tdd']).toContain('cli-testing');
464
+ expect(overlayState.knowledge['add-e2e-testing']).toBeDefined();
465
+ expect(overlayState.knowledge['add-e2e-testing']).toContain('cli-testing');
466
+ });
467
+ // Test 8: Overlay injects knowledge into foundational steps
468
+ it('overlay injects cli knowledge into foundational steps', async () => {
469
+ const { overlayState } = await resolveProjectOverlay('cli');
470
+ expect(overlayState.knowledge['create-prd']).toContain('cli-requirements');
471
+ expect(overlayState.knowledge['coding-standards']).toContain('cli-conventions');
472
+ expect(overlayState.knowledge['project-structure']).toContain('cli-project-structure');
473
+ });
474
+ // Test 9: Overlay injects knowledge into operations step
475
+ it('overlay injects cli knowledge into operations step', async () => {
476
+ const { overlayState } = await resolveProjectOverlay('cli');
477
+ expect(overlayState.knowledge['operations']).toBeDefined();
478
+ expect(overlayState.knowledge['operations']).toContain('cli-distribution-patterns');
479
+ expect(overlayState.knowledge['operations']).toContain('cli-shell-integration');
480
+ });
481
+ // Test 10: MVP methodology with cli overlay works
482
+ it('MVP methodology with cli overlay injects knowledge', async () => {
483
+ const { overlayState } = await resolveProjectOverlay('cli', 'mvp');
484
+ expect(overlayState.knowledge['system-architecture']).toContain('cli-architecture');
485
+ expect(overlayState.knowledge['tech-stack']).toContain('cli-distribution-patterns');
486
+ });
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
+ // Cross-type validation tests
749
+ // ---------------------------------------------------------------------------
750
+ describe('project-type overlay cross-validation', () => {
751
+ it('each overlay type injects distinct knowledge entries (no accidental overlap)', async () => {
752
+ const [webResult, backendResult, cliResult, libraryResult, mobileResult] = await Promise.all([
753
+ resolveProjectOverlay('web-app'),
754
+ resolveProjectOverlay('backend'),
755
+ resolveProjectOverlay('cli'),
756
+ resolveProjectOverlay('library'),
757
+ resolveProjectOverlay('mobile-app'),
758
+ ]);
759
+ // system-architecture should have type-specific entries for each
760
+ const webArch = webResult.overlayState.knowledge['system-architecture'] ?? [];
761
+ const backendArch = backendResult.overlayState.knowledge['system-architecture'] ?? [];
762
+ const cliArch = cliResult.overlayState.knowledge['system-architecture'] ?? [];
763
+ const libraryArch = libraryResult.overlayState.knowledge['system-architecture'] ?? [];
764
+ const mobileArch = mobileResult.overlayState.knowledge['system-architecture'] ?? [];
765
+ expect(webArch).toContain('web-app-architecture');
766
+ expect(webArch).not.toContain('backend-architecture');
767
+ expect(webArch).not.toContain('cli-architecture');
768
+ expect(webArch).not.toContain('library-architecture');
769
+ expect(webArch).not.toContain('mobile-app-architecture');
770
+ expect(backendArch).toContain('backend-architecture');
771
+ expect(backendArch).not.toContain('web-app-architecture');
772
+ expect(backendArch).not.toContain('cli-architecture');
773
+ expect(backendArch).not.toContain('library-architecture');
774
+ expect(backendArch).not.toContain('mobile-app-architecture');
775
+ expect(cliArch).toContain('cli-architecture');
776
+ expect(cliArch).not.toContain('web-app-architecture');
777
+ expect(cliArch).not.toContain('backend-architecture');
778
+ expect(cliArch).not.toContain('library-architecture');
779
+ expect(cliArch).not.toContain('mobile-app-architecture');
780
+ expect(libraryArch).toContain('library-architecture');
781
+ expect(libraryArch).not.toContain('web-app-architecture');
782
+ expect(libraryArch).not.toContain('backend-architecture');
783
+ expect(libraryArch).not.toContain('cli-architecture');
784
+ expect(libraryArch).not.toContain('mobile-app-architecture');
785
+ expect(mobileArch).toContain('mobile-app-architecture');
786
+ expect(mobileArch).not.toContain('web-app-architecture');
787
+ expect(mobileArch).not.toContain('backend-architecture');
788
+ expect(mobileArch).not.toContain('cli-architecture');
789
+ expect(mobileArch).not.toContain('library-architecture');
790
+ });
791
+ it('config schema rejects cross-typed config blocks', () => {
792
+ // gameConfig on web-app
793
+ expect(ConfigSchema.safeParse({
794
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
795
+ project: { projectType: 'web-app', gameConfig: { engine: 'unity' } },
796
+ }).success).toBe(false);
797
+ // webAppConfig on backend
798
+ expect(ConfigSchema.safeParse({
799
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
800
+ project: { projectType: 'backend', webAppConfig: { renderingStrategy: 'spa' } },
801
+ }).success).toBe(false);
802
+ // backendConfig on cli
803
+ expect(ConfigSchema.safeParse({
804
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
805
+ project: { projectType: 'cli', backendConfig: { apiStyle: 'rest' } },
806
+ }).success).toBe(false);
807
+ // cliConfig on game
808
+ expect(ConfigSchema.safeParse({
809
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
810
+ project: { projectType: 'game', cliConfig: { interactivity: 'hybrid' } },
811
+ }).success).toBe(false);
812
+ // libraryConfig on web-app
813
+ expect(ConfigSchema.safeParse({
814
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
815
+ project: { projectType: 'web-app', libraryConfig: { visibility: 'public' } },
816
+ }).success).toBe(false);
817
+ // mobileAppConfig on backend
818
+ expect(ConfigSchema.safeParse({
819
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
820
+ project: { projectType: 'backend', mobileAppConfig: { platform: 'ios' } },
821
+ }).success).toBe(false);
822
+ // libraryConfig on mobile-app
823
+ expect(ConfigSchema.safeParse({
824
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
825
+ project: { projectType: 'mobile-app', libraryConfig: { visibility: 'internal' } },
826
+ }).success).toBe(false);
827
+ // mobileAppConfig on library
828
+ expect(ConfigSchema.safeParse({
829
+ version: 2, methodology: 'deep', platforms: ['claude-code'],
830
+ project: { projectType: 'library', mobileAppConfig: { platform: 'android' } },
831
+ }).success).toBe(false);
832
+ });
833
+ });
834
+ //# sourceMappingURL=project-type-overlays.test.js.map