sofia-cli 0.1.1 → 0.1.4

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 (136) hide show
  1. package/README.md +42 -20
  2. package/dist/infra/deploy.sh +193 -0
  3. package/dist/infra/gather-env.sh +211 -0
  4. package/dist/infra/infra/deploy.sh +193 -0
  5. package/dist/infra/infra/gather-env.sh +211 -0
  6. package/dist/infra/infra/main.bicep +90 -0
  7. package/dist/infra/infra/main.bicepparam +18 -0
  8. package/dist/infra/infra/resources.bicep +134 -0
  9. package/dist/infra/infra/teardown.sh +114 -0
  10. package/dist/infra/main.bicep +90 -0
  11. package/dist/infra/main.bicepparam +18 -0
  12. package/dist/infra/resources.bicep +134 -0
  13. package/dist/infra/teardown.sh +114 -0
  14. package/dist/src/cli/developCommand.js +0 -2
  15. package/dist/src/cli/index.js +8 -1
  16. package/dist/src/cli/workshopCommand.js +1 -1
  17. package/dist/src/develop/index.js +1 -1
  18. package/dist/src/develop/pocUtils.js +228 -0
  19. package/dist/src/develop/ralphLoop.js +3 -3
  20. package/dist/src/shared/data/cards.json +655 -670
  21. package/docs/architecture.md +2 -1
  22. package/package.json +5 -3
  23. package/src/cli/developCommand.ts +1 -3
  24. package/src/cli/index.ts +11 -1
  25. package/src/cli/workshopCommand.ts +21 -17
  26. package/src/develop/dynamicScaffolder.ts +36 -30
  27. package/src/develop/index.ts +13 -2
  28. package/src/develop/pocUtils.ts +296 -0
  29. package/src/develop/ralphLoop.ts +8 -28
  30. package/src/develop/templateRegistry.ts +19 -18
  31. package/src/shared/data/cards.json +655 -670
  32. package/tests/e2e/developE2e.spec.ts +3 -61
  33. package/tests/e2e/developFailureE2e.spec.ts +34 -38
  34. package/tests/integration/pocGithubMcp.spec.ts +29 -39
  35. package/tests/integration/pocLocalFallback.spec.ts +29 -39
  36. package/tests/integration/ralphLoopFlow.spec.ts +46 -66
  37. package/tests/integration/ralphLoopPartial.spec.ts +30 -37
  38. package/tests/unit/develop/githubMcpAdapter.spec.ts +0 -134
  39. package/tests/unit/develop/outputValidator.spec.ts +45 -21
  40. package/tests/unit/develop/ralphLoop.spec.ts +58 -94
  41. package/tsconfig.json +2 -1
  42. package/vitest.workspace.ts +5 -0
  43. package/dist/src/develop/pocScaffolder.js +0 -542
  44. package/dist/tests/e2e/developE2e.spec.js +0 -126
  45. package/dist/tests/e2e/developFailureE2e.spec.js +0 -247
  46. package/dist/tests/e2e/developPty.spec.js +0 -75
  47. package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +0 -84
  48. package/dist/tests/e2e/harness.spec.js +0 -83
  49. package/dist/tests/e2e/mcpLive.spec.js +0 -120
  50. package/dist/tests/e2e/newSession.e2e.spec.js +0 -177
  51. package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +0 -62
  52. package/dist/tests/e2e/workiqEnrichment.spec.js +0 -56
  53. package/dist/tests/e2e/zavaSimulation.spec.js +0 -452
  54. package/dist/tests/fixtures/test-fixture-project/src/add.js +0 -3
  55. package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +0 -6
  56. package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +0 -8
  57. package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +0 -10
  58. package/dist/tests/fixtures/test-fixture-project/vitest.config.js +0 -6
  59. package/dist/tests/integration/autoStartConversation.spec.js +0 -138
  60. package/dist/tests/integration/defaultCommand.spec.js +0 -147
  61. package/dist/tests/integration/directCommandNonTty.spec.js +0 -224
  62. package/dist/tests/integration/directCommandTty.spec.js +0 -151
  63. package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +0 -175
  64. package/dist/tests/integration/exportArtifacts.spec.js +0 -202
  65. package/dist/tests/integration/exportFallbackFlow.spec.js +0 -99
  66. package/dist/tests/integration/mcpDegradationFlow.spec.js +0 -190
  67. package/dist/tests/integration/mcpTransportFlow.spec.js +0 -139
  68. package/dist/tests/integration/newSessionFlow.spec.js +0 -343
  69. package/dist/tests/integration/pocGithubMcp.spec.js +0 -186
  70. package/dist/tests/integration/pocLocalFallback.spec.js +0 -171
  71. package/dist/tests/integration/pocScaffold.spec.js +0 -163
  72. package/dist/tests/integration/ralphLoopFlow.spec.js +0 -359
  73. package/dist/tests/integration/ralphLoopPartial.spec.js +0 -368
  74. package/dist/tests/integration/resumeAndBacktrack.spec.js +0 -247
  75. package/dist/tests/integration/spinnerLifecycle.spec.js +0 -220
  76. package/dist/tests/integration/summarizationFlow.spec.js +0 -115
  77. package/dist/tests/integration/testRunnerReal.spec.js +0 -52
  78. package/dist/tests/integration/webSearchAgent.spec.js +0 -128
  79. package/dist/tests/live/copilotSdkLive.spec.js +0 -107
  80. package/dist/tests/live/zavaFullWorkshop.spec.js +0 -392
  81. package/dist/tests/setup/loadEnv.js +0 -3
  82. package/dist/tests/unit/cli/developCommand.spec.js +0 -567
  83. package/dist/tests/unit/cli/directCommands.spec.js +0 -279
  84. package/dist/tests/unit/cli/envLoader.spec.js +0 -58
  85. package/dist/tests/unit/cli/ioContext.spec.js +0 -119
  86. package/dist/tests/unit/cli/preflight.spec.js +0 -108
  87. package/dist/tests/unit/cli/statusCommand.spec.js +0 -111
  88. package/dist/tests/unit/cli/workshopClientFallback.spec.js +0 -80
  89. package/dist/tests/unit/cli/workshopCommand.spec.js +0 -329
  90. package/dist/tests/unit/config/vitestEnvSetup.spec.js +0 -13
  91. package/dist/tests/unit/develop/checkpointState.spec.js +0 -315
  92. package/dist/tests/unit/develop/codeGenerator.spec.js +0 -355
  93. package/dist/tests/unit/develop/githubMcpAdapter.spec.js +0 -231
  94. package/dist/tests/unit/develop/mcpContextEnricher.spec.js +0 -433
  95. package/dist/tests/unit/develop/outputValidator.spec.js +0 -119
  96. package/dist/tests/unit/develop/pocScaffolder.spec.js +0 -353
  97. package/dist/tests/unit/develop/ralphLoop.spec.js +0 -1248
  98. package/dist/tests/unit/develop/templateRegistry.spec.js +0 -85
  99. package/dist/tests/unit/develop/testRunner.spec.js +0 -249
  100. package/dist/tests/unit/infraBicep.spec.js +0 -92
  101. package/dist/tests/unit/infraDeploy.spec.js +0 -82
  102. package/dist/tests/unit/infraTeardown.spec.js +0 -63
  103. package/dist/tests/unit/logging/logger.spec.js +0 -43
  104. package/dist/tests/unit/loop/conversationLoop.spec.js +0 -592
  105. package/dist/tests/unit/loop/phaseSummarizer.spec.js +0 -141
  106. package/dist/tests/unit/loop/streamingMarkdown.spec.js +0 -147
  107. package/dist/tests/unit/mcp/mcpManager.spec.js +0 -279
  108. package/dist/tests/unit/mcp/mcpTransport.spec.js +0 -529
  109. package/dist/tests/unit/mcp/retryPolicy.spec.js +0 -218
  110. package/dist/tests/unit/mcp/timeoutValidation.spec.js +0 -46
  111. package/dist/tests/unit/mcp/webSearch.spec.js +0 -567
  112. package/dist/tests/unit/phases/contextSummarizer.spec.js +0 -140
  113. package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +0 -93
  114. package/dist/tests/unit/phases/discoveryEnricher.spec.js +0 -411
  115. package/dist/tests/unit/phases/phaseExtractors.spec.js +0 -352
  116. package/dist/tests/unit/phases/phaseHandlers.spec.js +0 -425
  117. package/dist/tests/unit/prompts/promptLoader.spec.js +0 -118
  118. package/dist/tests/unit/schemas/pocSchemas.spec.js +0 -412
  119. package/dist/tests/unit/schemas/session.spec.js +0 -257
  120. package/dist/tests/unit/sessions/exportPaths.spec.js +0 -31
  121. package/dist/tests/unit/sessions/exportWriter.spec.js +0 -655
  122. package/dist/tests/unit/sessions/sessionManager.spec.js +0 -151
  123. package/dist/tests/unit/sessions/sessionStore.spec.js +0 -116
  124. package/dist/tests/unit/shared/activitySpinner.spec.js +0 -175
  125. package/dist/tests/unit/shared/cardsLoader.spec.js +0 -76
  126. package/dist/tests/unit/shared/copilotClient.spec.js +0 -155
  127. package/dist/tests/unit/shared/errorClassifier.spec.js +0 -131
  128. package/dist/tests/unit/shared/events.spec.js +0 -55
  129. package/dist/tests/unit/shared/markdownRenderer.spec.js +0 -35
  130. package/dist/tests/unit/shared/markdownRendererChunks.spec.js +0 -70
  131. package/dist/tests/unit/shared/tableRenderer.spec.js +0 -34
  132. package/dist/vitest.config.js +0 -14
  133. package/dist/vitest.live.config.js +0 -18
  134. package/src/develop/pocScaffolder.ts +0 -646
  135. package/tests/integration/pocScaffold.spec.ts +0 -220
  136. package/tests/unit/develop/pocScaffolder.spec.ts +0 -451
@@ -1,412 +0,0 @@
1
- /**
2
- * T008: Unit tests for new/extended PoC schemas (Feature 002).
3
- *
4
- * Tests TechStack, TestResults, TestFailure, extended PocIteration,
5
- * and extended PocDevelopmentState schemas.
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import { techStackSchema, testFailureSchema, testResultsSchema, pocIterationSchema, pocDevelopmentStateSchema, } from '../../../src/shared/schemas/session.js';
9
- // ── TechStack ────────────────────────────────────────────────────────────────
10
- describe('techStackSchema', () => {
11
- it('parses a minimal valid tech stack (required fields only)', () => {
12
- const result = techStackSchema.safeParse({
13
- language: 'TypeScript',
14
- testRunner: 'npm test',
15
- runtime: 'Node.js 20',
16
- });
17
- expect(result.success).toBe(true);
18
- if (result.success) {
19
- expect(result.data.language).toBe('TypeScript');
20
- expect(result.data.testRunner).toBe('npm test');
21
- expect(result.data.runtime).toBe('Node.js 20');
22
- expect(result.data.framework).toBeUndefined();
23
- expect(result.data.buildCommand).toBeUndefined();
24
- }
25
- });
26
- it('parses a full tech stack with optional fields', () => {
27
- const result = techStackSchema.safeParse({
28
- language: 'TypeScript',
29
- framework: 'Express',
30
- testRunner: 'npm test',
31
- buildCommand: 'npm run build',
32
- runtime: 'Node.js 20',
33
- });
34
- expect(result.success).toBe(true);
35
- if (result.success) {
36
- expect(result.data.framework).toBe('Express');
37
- expect(result.data.buildCommand).toBe('npm run build');
38
- }
39
- });
40
- it('rejects a tech stack missing required language', () => {
41
- const result = techStackSchema.safeParse({
42
- testRunner: 'npm test',
43
- runtime: 'Node.js 20',
44
- });
45
- expect(result.success).toBe(false);
46
- });
47
- it('rejects a tech stack missing required testRunner', () => {
48
- const result = techStackSchema.safeParse({
49
- language: 'TypeScript',
50
- runtime: 'Node.js 20',
51
- });
52
- expect(result.success).toBe(false);
53
- });
54
- it('rejects a tech stack missing required runtime', () => {
55
- const result = techStackSchema.safeParse({
56
- language: 'TypeScript',
57
- testRunner: 'npm test',
58
- });
59
- expect(result.success).toBe(false);
60
- });
61
- });
62
- // ── TestFailure ──────────────────────────────────────────────────────────────
63
- describe('testFailureSchema', () => {
64
- it('parses a minimal test failure', () => {
65
- const result = testFailureSchema.safeParse({
66
- testName: 'route optimizer > should return shortest path',
67
- message: 'Expected 3 but got 5',
68
- });
69
- expect(result.success).toBe(true);
70
- });
71
- it('parses a full test failure with optional fields', () => {
72
- const result = testFailureSchema.safeParse({
73
- testName: 'route optimizer > should return shortest path',
74
- message: 'Expected 3 but got 5',
75
- expected: '3',
76
- actual: '5',
77
- file: 'tests/optimizer.test.ts',
78
- line: 42,
79
- });
80
- expect(result.success).toBe(true);
81
- if (result.success) {
82
- expect(result.data.expected).toBe('3');
83
- expect(result.data.actual).toBe('5');
84
- expect(result.data.file).toBe('tests/optimizer.test.ts');
85
- expect(result.data.line).toBe(42);
86
- }
87
- });
88
- it('rejects a failure missing testName', () => {
89
- const result = testFailureSchema.safeParse({
90
- message: 'Expected 3 but got 5',
91
- });
92
- expect(result.success).toBe(false);
93
- });
94
- it('rejects a failure missing message', () => {
95
- const result = testFailureSchema.safeParse({
96
- testName: 'route optimizer > should return shortest path',
97
- });
98
- expect(result.success).toBe(false);
99
- });
100
- });
101
- // ── TestResults ──────────────────────────────────────────────────────────────
102
- describe('testResultsSchema', () => {
103
- it('parses valid test results where total = passed + failed + skipped', () => {
104
- const result = testResultsSchema.safeParse({
105
- passed: 3,
106
- failed: 1,
107
- skipped: 1,
108
- total: 5,
109
- durationMs: 1234,
110
- failures: [{ testName: 'test A', message: 'err' }],
111
- });
112
- expect(result.success).toBe(true);
113
- });
114
- it('rejects test results where total !== passed + failed + skipped', () => {
115
- const result = testResultsSchema.safeParse({
116
- passed: 3,
117
- failed: 1,
118
- skipped: 0,
119
- total: 10, // wrong
120
- durationMs: 1234,
121
- failures: [],
122
- });
123
- expect(result.success).toBe(false);
124
- if (!result.success) {
125
- expect(result.error.issues[0].message).toContain('total must equal');
126
- }
127
- });
128
- it('parses results with zero failures', () => {
129
- const result = testResultsSchema.safeParse({
130
- passed: 5,
131
- failed: 0,
132
- skipped: 0,
133
- total: 5,
134
- durationMs: 500,
135
- failures: [],
136
- });
137
- expect(result.success).toBe(true);
138
- });
139
- it('parses results with optional rawOutput', () => {
140
- const result = testResultsSchema.safeParse({
141
- passed: 1,
142
- failed: 0,
143
- skipped: 0,
144
- total: 1,
145
- durationMs: 100,
146
- failures: [],
147
- rawOutput: 'PASS tests/index.test.ts',
148
- });
149
- expect(result.success).toBe(true);
150
- if (result.success) {
151
- expect(result.data.rawOutput).toBe('PASS tests/index.test.ts');
152
- }
153
- });
154
- it('allows rawOutput to be omitted', () => {
155
- const result = testResultsSchema.safeParse({
156
- passed: 1,
157
- failed: 0,
158
- skipped: 0,
159
- total: 1,
160
- durationMs: 100,
161
- failures: [],
162
- });
163
- expect(result.success).toBe(true);
164
- if (result.success) {
165
- expect(result.data.rawOutput).toBeUndefined();
166
- }
167
- });
168
- it('handles multiple test failures', () => {
169
- const result = testResultsSchema.safeParse({
170
- passed: 0,
171
- failed: 2,
172
- skipped: 0,
173
- total: 2,
174
- durationMs: 800,
175
- failures: [
176
- { testName: 'test A', message: 'err A', file: 'tests/a.test.ts', line: 10 },
177
- { testName: 'test B', message: 'err B', expected: 'foo', actual: 'bar' },
178
- ],
179
- });
180
- expect(result.success).toBe(true);
181
- if (result.success) {
182
- expect(result.data.failures).toHaveLength(2);
183
- }
184
- });
185
- });
186
- // ── PocIteration (extended) ─────────────────────────────────────────────────
187
- describe('pocIterationSchema (extended)', () => {
188
- it('parses a scaffold iteration', () => {
189
- const result = pocIterationSchema.safeParse({
190
- iteration: 1,
191
- startedAt: '2026-01-15T10:00:00Z',
192
- endedAt: '2026-01-15T10:01:00Z',
193
- outcome: 'scaffold',
194
- filesChanged: ['package.json', 'src/index.ts', 'tests/index.test.ts'],
195
- });
196
- expect(result.success).toBe(true);
197
- if (result.success) {
198
- expect(result.data.outcome).toBe('scaffold');
199
- expect(result.data.filesChanged).toHaveLength(3);
200
- }
201
- });
202
- it('parses a tests-passing iteration with testResults', () => {
203
- const result = pocIterationSchema.safeParse({
204
- iteration: 2,
205
- startedAt: '2026-01-15T10:01:00Z',
206
- endedAt: '2026-01-15T10:02:00Z',
207
- outcome: 'tests-passing',
208
- filesChanged: ['src/index.ts'],
209
- testResults: {
210
- passed: 3,
211
- failed: 0,
212
- skipped: 0,
213
- total: 3,
214
- durationMs: 450,
215
- failures: [],
216
- },
217
- });
218
- expect(result.success).toBe(true);
219
- });
220
- it('parses a tests-failing iteration with failures', () => {
221
- const result = pocIterationSchema.safeParse({
222
- iteration: 3,
223
- startedAt: '2026-01-15T10:02:00Z',
224
- outcome: 'tests-failing',
225
- filesChanged: ['src/optimizer.ts'],
226
- testResults: {
227
- passed: 1,
228
- failed: 2,
229
- skipped: 0,
230
- total: 3,
231
- durationMs: 600,
232
- failures: [{ testName: 'optimizer test', message: 'wrong output' }],
233
- },
234
- });
235
- expect(result.success).toBe(true);
236
- });
237
- it('parses an error iteration with errorMessage', () => {
238
- const result = pocIterationSchema.safeParse({
239
- iteration: 4,
240
- startedAt: '2026-01-15T10:03:00Z',
241
- outcome: 'error',
242
- filesChanged: [],
243
- errorMessage: 'npm install failed: ENOTFOUND registry.npmjs.org',
244
- });
245
- expect(result.success).toBe(true);
246
- if (result.success) {
247
- expect(result.data.errorMessage).toContain('npm install failed');
248
- }
249
- });
250
- it('rejects invalid outcome enum value', () => {
251
- const result = pocIterationSchema.safeParse({
252
- iteration: 1,
253
- startedAt: '2026-01-15T10:00:00Z',
254
- outcome: 'unknown-outcome',
255
- filesChanged: [],
256
- });
257
- expect(result.success).toBe(false);
258
- });
259
- it('defaults outcome to scaffold when omitted', () => {
260
- const result = pocIterationSchema.safeParse({
261
- iteration: 1,
262
- startedAt: '2026-01-15T10:00:00Z',
263
- filesChanged: [],
264
- });
265
- expect(result.success).toBe(true);
266
- if (result.success) {
267
- expect(result.data.outcome).toBe('scaffold');
268
- }
269
- });
270
- it('defaults filesChanged to empty array when omitted', () => {
271
- const result = pocIterationSchema.safeParse({
272
- iteration: 1,
273
- startedAt: '2026-01-15T10:00:00Z',
274
- outcome: 'scaffold',
275
- });
276
- expect(result.success).toBe(true);
277
- if (result.success) {
278
- expect(result.data.filesChanged).toEqual([]);
279
- }
280
- });
281
- it('preserves optional testsRun field for backward compatibility', () => {
282
- const result = pocIterationSchema.safeParse({
283
- iteration: 1,
284
- startedAt: '2026-01-15T10:00:00Z',
285
- outcome: 'scaffold',
286
- filesChanged: [],
287
- testsRun: ['tests/old.spec.ts'], // legacy field
288
- });
289
- expect(result.success).toBe(true);
290
- if (result.success) {
291
- expect(result.data.testsRun).toEqual(['tests/old.spec.ts']);
292
- }
293
- });
294
- it('parses iteration with llmPromptContext for auditability', () => {
295
- const result = pocIterationSchema.safeParse({
296
- iteration: 2,
297
- startedAt: '2026-01-15T10:01:00Z',
298
- outcome: 'tests-failing',
299
- filesChanged: [],
300
- llmPromptContext: 'Iteration 2 of 5, 2 failures, files: src/index.ts',
301
- });
302
- expect(result.success).toBe(true);
303
- if (result.success) {
304
- expect(result.data.llmPromptContext).toContain('Iteration 2');
305
- }
306
- });
307
- });
308
- // ── PocDevelopmentState (extended) ─────────────────────────────────────────
309
- describe('pocDevelopmentStateSchema (extended)', () => {
310
- it('parses a minimal poc state with repoSource=local', () => {
311
- const result = pocDevelopmentStateSchema.safeParse({
312
- repoSource: 'local',
313
- iterations: [],
314
- });
315
- expect(result.success).toBe(true);
316
- });
317
- it('rejects a poc state with repoSource=github-mcp (always-local policy)', () => {
318
- const result = pocDevelopmentStateSchema.safeParse({
319
- repoSource: 'github-mcp',
320
- repoUrl: 'https://github.com/acme/poc-route-optimizer',
321
- iterations: [],
322
- });
323
- expect(result.success).toBe(false);
324
- });
325
- it('parses a completed poc state with all fields', () => {
326
- const result = pocDevelopmentStateSchema.safeParse({
327
- repoSource: 'local',
328
- repoPath: './poc/fixture-session-001',
329
- techStack: {
330
- language: 'TypeScript',
331
- testRunner: 'npm test',
332
- runtime: 'Node.js 20',
333
- },
334
- iterations: [
335
- {
336
- iteration: 1,
337
- startedAt: '2026-01-15T10:00:00Z',
338
- outcome: 'scaffold',
339
- filesChanged: ['package.json'],
340
- },
341
- ],
342
- finalStatus: 'success',
343
- terminationReason: 'tests-passing',
344
- totalDurationMs: 45000,
345
- finalTestResults: {
346
- passed: 3,
347
- failed: 0,
348
- skipped: 0,
349
- total: 3,
350
- durationMs: 400,
351
- failures: [],
352
- },
353
- });
354
- expect(result.success).toBe(true);
355
- if (result.success) {
356
- expect(result.data.finalStatus).toBe('success');
357
- expect(result.data.terminationReason).toBe('tests-passing');
358
- }
359
- });
360
- it('accepts finalStatus "partial" (new in Feature 002)', () => {
361
- const result = pocDevelopmentStateSchema.safeParse({
362
- repoSource: 'local',
363
- iterations: [],
364
- finalStatus: 'partial',
365
- terminationReason: 'max-iterations',
366
- });
367
- expect(result.success).toBe(true);
368
- if (result.success) {
369
- expect(result.data.finalStatus).toBe('partial');
370
- }
371
- });
372
- it('rejects invalid repoSource', () => {
373
- const result = pocDevelopmentStateSchema.safeParse({
374
- repoSource: 'bitbucket',
375
- iterations: [],
376
- });
377
- expect(result.success).toBe(false);
378
- });
379
- it('rejects invalid terminationReason', () => {
380
- const result = pocDevelopmentStateSchema.safeParse({
381
- repoSource: 'local',
382
- iterations: [],
383
- terminationReason: 'timeout',
384
- });
385
- expect(result.success).toBe(false);
386
- });
387
- it('rejects invalid finalStatus', () => {
388
- const result = pocDevelopmentStateSchema.safeParse({
389
- repoSource: 'local',
390
- iterations: [],
391
- finalStatus: 'abandoned',
392
- });
393
- expect(result.success).toBe(false);
394
- });
395
- it('rejects missing repoSource', () => {
396
- const result = pocDevelopmentStateSchema.safeParse({
397
- iterations: [],
398
- });
399
- expect(result.success).toBe(false);
400
- });
401
- it('accepts all valid terminationReason values', () => {
402
- const reasons = ['tests-passing', 'max-iterations', 'user-stopped', 'error'];
403
- for (const reason of reasons) {
404
- const result = pocDevelopmentStateSchema.safeParse({
405
- repoSource: 'local',
406
- iterations: [],
407
- terminationReason: reason,
408
- });
409
- expect(result.success, `Expected ${reason} to be valid`).toBe(true);
410
- }
411
- });
412
- });
@@ -1,257 +0,0 @@
1
- /**
2
- * Unit tests for WorkshopSession Zod schemas.
3
- *
4
- * Validates that the session data model matches the spec in data-model.md
5
- * and the contract in contracts/session-json.md.
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import { Phase, SessionStatus, workshopSessionSchema, } from '../../../src/shared/schemas/session.js';
9
- // ── Helpers ──────────────────────────────────────────────────────────────────
10
- function validSession(overrides = {}) {
11
- return {
12
- sessionId: 'test-001',
13
- schemaVersion: '1',
14
- createdAt: '2026-01-01T00:00:00Z',
15
- updatedAt: '2026-01-01T00:00:00Z',
16
- phase: 'Discover',
17
- status: 'Active',
18
- participants: [],
19
- artifacts: { generatedFiles: [] },
20
- ...overrides,
21
- };
22
- }
23
- // ── Phase enum ───────────────────────────────────────────────────────────────
24
- describe('Phase', () => {
25
- it('contains all seven governed phases', () => {
26
- const expected = [
27
- 'Discover',
28
- 'Ideate',
29
- 'Design',
30
- 'Select',
31
- 'Plan',
32
- 'Develop',
33
- 'Complete',
34
- ];
35
- expect(Phase).toEqual(expected);
36
- });
37
- });
38
- // ── SessionStatus enum ───────────────────────────────────────────────────────
39
- describe('SessionStatus', () => {
40
- it('contains Active, Paused, Completed, Errored', () => {
41
- expect(SessionStatus).toEqual(['Active', 'Paused', 'Completed', 'Errored']);
42
- });
43
- });
44
- // ── workshopSessionSchema ────────────────────────────────────────────────────
45
- describe('workshopSessionSchema', () => {
46
- it('parses a minimal valid session', () => {
47
- const result = workshopSessionSchema.parse(validSession());
48
- expect(result.sessionId).toBe('test-001');
49
- expect(result.phase).toBe('Discover');
50
- expect(result.status).toBe('Active');
51
- });
52
- it('rejects missing required field sessionId', () => {
53
- const data = validSession();
54
- delete data.sessionId;
55
- expect(() => workshopSessionSchema.parse(data)).toThrow();
56
- });
57
- it('rejects invalid phase value', () => {
58
- expect(() => workshopSessionSchema.parse(validSession({ phase: 'InvalidPhase' }))).toThrow();
59
- });
60
- it('rejects invalid status value', () => {
61
- expect(() => workshopSessionSchema.parse(validSession({ status: 'Nope' }))).toThrow();
62
- });
63
- it('accepts optional entity fields', () => {
64
- const result = workshopSessionSchema.parse(validSession({
65
- businessContext: {
66
- businessDescription: 'A retail company',
67
- challenges: ['inventory tracking'],
68
- },
69
- topic: { topicArea: 'Supply Chain' },
70
- }));
71
- expect(result.businessContext?.businessDescription).toBe('A retail company');
72
- expect(result.topic?.topicArea).toBe('Supply Chain');
73
- });
74
- it('preserves unknown extra fields (forward compat)', () => {
75
- const base = validSession();
76
- base.futureField = 'hello';
77
- const result = workshopSessionSchema.parse(base);
78
- // Zod passthrough should keep the extra field
79
- expect(result.futureField).toBe('hello');
80
- });
81
- it('handles a full session with ideas and evaluation', () => {
82
- const result = workshopSessionSchema.parse(validSession({
83
- ideas: [
84
- {
85
- id: 'idea-1',
86
- title: 'Smart Onboarding',
87
- description: 'AI-guided onboarding',
88
- workflowStepIds: ['step-1'],
89
- },
90
- ],
91
- evaluation: {
92
- ideas: [
93
- {
94
- ideaId: 'idea-1',
95
- feasibility: 4,
96
- value: 5,
97
- },
98
- ],
99
- method: 'feasibility-value-matrix',
100
- },
101
- }));
102
- expect(result.ideas).toHaveLength(1);
103
- expect(result.evaluation?.method).toBe('feasibility-value-matrix');
104
- });
105
- it('validates participant roles', () => {
106
- const result = workshopSessionSchema.parse(validSession({
107
- participants: [
108
- { id: 'p1', displayName: 'Alice', role: 'Facilitator' },
109
- { id: 'p2', displayName: 'Bob', role: 'Attendee' },
110
- ],
111
- }));
112
- expect(result.participants).toHaveLength(2);
113
- });
114
- it('rejects invalid participant role', () => {
115
- expect(() => workshopSessionSchema.parse(validSession({
116
- participants: [{ id: 'p1', displayName: 'Alice', role: 'Admin' }],
117
- }))).toThrow();
118
- });
119
- it('validates ConversationTurn array in turns', () => {
120
- const result = workshopSessionSchema.parse(validSession({
121
- turns: [
122
- {
123
- phase: 'Discover',
124
- sequence: 1,
125
- role: 'user',
126
- content: 'We are a retail company',
127
- timestamp: '2026-01-01T00:01:00Z',
128
- },
129
- {
130
- phase: 'Discover',
131
- sequence: 2,
132
- role: 'assistant',
133
- content: 'Got it! Tell me more about your challenges.',
134
- timestamp: '2026-01-01T00:02:00Z',
135
- },
136
- ],
137
- }));
138
- expect(result.turns).toHaveLength(2);
139
- });
140
- it('validates ArtifactIndex with generated files', () => {
141
- const result = workshopSessionSchema.parse(validSession({
142
- artifacts: {
143
- exportDir: './exports/test-001/',
144
- generatedFiles: [
145
- {
146
- relativePath: 'discover.md',
147
- type: 'markdown',
148
- createdAt: '2026-01-01T00:05:00Z',
149
- },
150
- ],
151
- },
152
- }));
153
- expect(result.artifacts.generatedFiles).toHaveLength(1);
154
- });
155
- it('validates PocDevelopmentState', () => {
156
- const result = workshopSessionSchema.parse(validSession({
157
- poc: {
158
- repoSource: 'local',
159
- iterations: [],
160
- },
161
- }));
162
- expect(result.poc?.iterations).toEqual([]);
163
- });
164
- it('validates ImplementationPlan with milestones', () => {
165
- const result = workshopSessionSchema.parse(validSession({
166
- plan: {
167
- milestones: [
168
- { id: 'm1', title: 'Phase 1', items: ['Setup', 'Build'] },
169
- ],
170
- },
171
- }));
172
- expect(result.plan?.milestones).toHaveLength(1);
173
- });
174
- it('validates BXT evaluation with classification', () => {
175
- const result = workshopSessionSchema.parse(validSession({
176
- evaluation: {
177
- ideas: [
178
- {
179
- ideaId: 'idea-1',
180
- feasibility: 3,
181
- value: 4,
182
- risks: ['Data quality'],
183
- dataNeeded: ['CRM data'],
184
- humanValue: ['Time savings'],
185
- kpisInfluenced: ['CSAT'],
186
- },
187
- ],
188
- method: 'feasibility-value-matrix',
189
- },
190
- }));
191
- expect(result.evaluation?.ideas[0].risks).toContain('Data quality');
192
- });
193
- it('validates SelectedIdea', () => {
194
- const result = workshopSessionSchema.parse(validSession({
195
- selection: {
196
- ideaId: 'idea-1',
197
- selectionRationale: 'Highest BXT score',
198
- confirmedByUser: true,
199
- confirmedAt: '2026-01-01T01:00:00Z',
200
- },
201
- }));
202
- expect(result.selection?.confirmedByUser).toBe(true);
203
- });
204
- it('validates WorkflowMap with steps and edges', () => {
205
- const result = workshopSessionSchema.parse(validSession({
206
- workflow: {
207
- activities: [
208
- { id: 'step-1', name: 'Receive Order' },
209
- { id: 'step-2', name: 'Process Payment' },
210
- ],
211
- edges: [{ fromStepId: 'step-1', toStepId: 'step-2' }],
212
- },
213
- }));
214
- expect(result.workflow?.activities).toHaveLength(2);
215
- expect(result.workflow?.edges).toHaveLength(1);
216
- });
217
- it('validates CardSelection', () => {
218
- const result = workshopSessionSchema.parse(validSession({
219
- cards: {
220
- selectedCards: [
221
- { id: 'extract-information', title: 'Extract Information' },
222
- ],
223
- scores: [
224
- {
225
- cardId: 'extract-information',
226
- dimensions: { relevance: 5, feasibility: 3 },
227
- },
228
- ],
229
- },
230
- }));
231
- expect(result.cards?.selectedCards).toHaveLength(1);
232
- });
233
- // ── T061: name field tests ─────────────────────────────────────────────────
234
- it('accepts optional name field as string', () => {
235
- const result = workshopSessionSchema.parse(validSession({ name: 'Logistics AI Routing' }));
236
- expect(result.name).toBe('Logistics AI Routing');
237
- });
238
- it('omits name field gracefully when not provided', () => {
239
- const result = workshopSessionSchema.parse(validSession());
240
- expect(result.name).toBeUndefined();
241
- });
242
- it('rejects non-string name field', () => {
243
- expect(() => workshopSessionSchema.parse(validSession({ name: 42 }))).toThrow();
244
- });
245
- it('validates ErrorRecord array', () => {
246
- const result = workshopSessionSchema.parse(validSession({
247
- errors: [
248
- {
249
- timestamp: '2026-01-01T00:10:00Z',
250
- code: 'MCP_TIMEOUT',
251
- message: 'WorkIQ timed out after 30s',
252
- },
253
- ],
254
- }));
255
- expect(result.errors).toHaveLength(1);
256
- });
257
- });