sofia-cli 0.1.2 → 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.
- package/README.md +42 -20
- package/dist/infra/deploy.sh +193 -0
- package/dist/infra/gather-env.sh +211 -0
- package/dist/infra/infra/deploy.sh +193 -0
- package/dist/infra/infra/gather-env.sh +211 -0
- package/dist/infra/infra/main.bicep +90 -0
- package/dist/infra/infra/main.bicepparam +18 -0
- package/dist/infra/infra/resources.bicep +134 -0
- package/dist/infra/infra/teardown.sh +114 -0
- package/dist/infra/main.bicep +90 -0
- package/dist/infra/main.bicepparam +18 -0
- package/dist/infra/resources.bicep +134 -0
- package/dist/infra/teardown.sh +114 -0
- package/dist/src/cli/developCommand.js +0 -2
- package/dist/src/cli/index.js +8 -1
- package/dist/src/cli/workshopCommand.js +1 -1
- package/dist/src/develop/index.js +1 -1
- package/dist/src/develop/pocUtils.js +228 -0
- package/dist/src/develop/ralphLoop.js +8 -27
- package/dist/src/shared/data/cards.json +655 -670
- package/docs/architecture.md +2 -1
- package/package.json +5 -3
- package/src/cli/developCommand.ts +1 -3
- package/src/cli/index.ts +11 -1
- package/src/cli/workshopCommand.ts +21 -17
- package/src/develop/dynamicScaffolder.ts +36 -30
- package/src/develop/index.ts +13 -2
- package/src/develop/pocUtils.ts +296 -0
- package/src/develop/ralphLoop.ts +8 -28
- package/src/develop/templateRegistry.ts +19 -18
- package/src/shared/data/cards.json +655 -670
- package/tests/e2e/developE2e.spec.ts +3 -61
- package/tests/e2e/developFailureE2e.spec.ts +34 -38
- package/tests/integration/pocGithubMcp.spec.ts +29 -39
- package/tests/integration/pocLocalFallback.spec.ts +29 -39
- package/tests/integration/ralphLoopFlow.spec.ts +46 -66
- package/tests/integration/ralphLoopPartial.spec.ts +30 -37
- package/tests/unit/develop/githubMcpAdapter.spec.ts +0 -134
- package/tests/unit/develop/outputValidator.spec.ts +45 -21
- package/tests/unit/develop/ralphLoop.spec.ts +58 -94
- package/tsconfig.json +2 -1
- package/vitest.workspace.ts +5 -0
- package/dist/src/develop/pocScaffolder.js +0 -542
- package/dist/tests/e2e/developE2e.spec.js +0 -126
- package/dist/tests/e2e/developFailureE2e.spec.js +0 -247
- package/dist/tests/e2e/developPty.spec.js +0 -75
- package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +0 -84
- package/dist/tests/e2e/harness.spec.js +0 -83
- package/dist/tests/e2e/mcpLive.spec.js +0 -120
- package/dist/tests/e2e/newSession.e2e.spec.js +0 -177
- package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +0 -62
- package/dist/tests/e2e/workiqEnrichment.spec.js +0 -56
- package/dist/tests/e2e/zavaSimulation.spec.js +0 -452
- package/dist/tests/fixtures/test-fixture-project/src/add.js +0 -3
- package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +0 -6
- package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +0 -8
- package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +0 -10
- package/dist/tests/fixtures/test-fixture-project/vitest.config.js +0 -6
- package/dist/tests/integration/autoStartConversation.spec.js +0 -138
- package/dist/tests/integration/defaultCommand.spec.js +0 -147
- package/dist/tests/integration/directCommandNonTty.spec.js +0 -224
- package/dist/tests/integration/directCommandTty.spec.js +0 -151
- package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +0 -175
- package/dist/tests/integration/exportArtifacts.spec.js +0 -202
- package/dist/tests/integration/exportFallbackFlow.spec.js +0 -99
- package/dist/tests/integration/mcpDegradationFlow.spec.js +0 -190
- package/dist/tests/integration/mcpTransportFlow.spec.js +0 -139
- package/dist/tests/integration/newSessionFlow.spec.js +0 -343
- package/dist/tests/integration/pocGithubMcp.spec.js +0 -186
- package/dist/tests/integration/pocLocalFallback.spec.js +0 -171
- package/dist/tests/integration/pocScaffold.spec.js +0 -163
- package/dist/tests/integration/ralphLoopFlow.spec.js +0 -359
- package/dist/tests/integration/ralphLoopPartial.spec.js +0 -368
- package/dist/tests/integration/resumeAndBacktrack.spec.js +0 -247
- package/dist/tests/integration/spinnerLifecycle.spec.js +0 -220
- package/dist/tests/integration/summarizationFlow.spec.js +0 -115
- package/dist/tests/integration/testRunnerReal.spec.js +0 -52
- package/dist/tests/integration/webSearchAgent.spec.js +0 -128
- package/dist/tests/live/copilotSdkLive.spec.js +0 -107
- package/dist/tests/live/zavaFullWorkshop.spec.js +0 -392
- package/dist/tests/setup/loadEnv.js +0 -3
- package/dist/tests/unit/cli/developCommand.spec.js +0 -567
- package/dist/tests/unit/cli/directCommands.spec.js +0 -279
- package/dist/tests/unit/cli/envLoader.spec.js +0 -58
- package/dist/tests/unit/cli/ioContext.spec.js +0 -119
- package/dist/tests/unit/cli/preflight.spec.js +0 -108
- package/dist/tests/unit/cli/statusCommand.spec.js +0 -111
- package/dist/tests/unit/cli/workshopClientFallback.spec.js +0 -80
- package/dist/tests/unit/cli/workshopCommand.spec.js +0 -328
- package/dist/tests/unit/config/vitestEnvSetup.spec.js +0 -13
- package/dist/tests/unit/develop/checkpointState.spec.js +0 -315
- package/dist/tests/unit/develop/codeGenerator.spec.js +0 -355
- package/dist/tests/unit/develop/githubMcpAdapter.spec.js +0 -231
- package/dist/tests/unit/develop/mcpContextEnricher.spec.js +0 -433
- package/dist/tests/unit/develop/outputValidator.spec.js +0 -119
- package/dist/tests/unit/develop/pocScaffolder.spec.js +0 -353
- package/dist/tests/unit/develop/ralphLoop.spec.js +0 -1248
- package/dist/tests/unit/develop/templateRegistry.spec.js +0 -85
- package/dist/tests/unit/develop/testRunner.spec.js +0 -249
- package/dist/tests/unit/infraBicep.spec.js +0 -92
- package/dist/tests/unit/infraDeploy.spec.js +0 -82
- package/dist/tests/unit/infraTeardown.spec.js +0 -63
- package/dist/tests/unit/logging/logger.spec.js +0 -43
- package/dist/tests/unit/loop/conversationLoop.spec.js +0 -592
- package/dist/tests/unit/loop/phaseSummarizer.spec.js +0 -141
- package/dist/tests/unit/loop/streamingMarkdown.spec.js +0 -147
- package/dist/tests/unit/mcp/mcpManager.spec.js +0 -279
- package/dist/tests/unit/mcp/mcpTransport.spec.js +0 -529
- package/dist/tests/unit/mcp/retryPolicy.spec.js +0 -218
- package/dist/tests/unit/mcp/timeoutValidation.spec.js +0 -46
- package/dist/tests/unit/mcp/webSearch.spec.js +0 -567
- package/dist/tests/unit/phases/contextSummarizer.spec.js +0 -140
- package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +0 -93
- package/dist/tests/unit/phases/discoveryEnricher.spec.js +0 -411
- package/dist/tests/unit/phases/phaseExtractors.spec.js +0 -352
- package/dist/tests/unit/phases/phaseHandlers.spec.js +0 -425
- package/dist/tests/unit/prompts/promptLoader.spec.js +0 -118
- package/dist/tests/unit/schemas/pocSchemas.spec.js +0 -412
- package/dist/tests/unit/schemas/session.spec.js +0 -257
- package/dist/tests/unit/sessions/exportPaths.spec.js +0 -31
- package/dist/tests/unit/sessions/exportWriter.spec.js +0 -655
- package/dist/tests/unit/sessions/sessionManager.spec.js +0 -151
- package/dist/tests/unit/sessions/sessionStore.spec.js +0 -116
- package/dist/tests/unit/shared/activitySpinner.spec.js +0 -175
- package/dist/tests/unit/shared/cardsLoader.spec.js +0 -76
- package/dist/tests/unit/shared/copilotClient.spec.js +0 -155
- package/dist/tests/unit/shared/errorClassifier.spec.js +0 -131
- package/dist/tests/unit/shared/events.spec.js +0 -55
- package/dist/tests/unit/shared/markdownRenderer.spec.js +0 -35
- package/dist/tests/unit/shared/markdownRendererChunks.spec.js +0 -70
- package/dist/tests/unit/shared/tableRenderer.spec.js +0 -34
- package/dist/vitest.config.js +0 -14
- package/dist/vitest.live.config.js +0 -18
- package/src/develop/pocScaffolder.ts +0 -646
- package/tests/integration/pocScaffold.spec.ts +0 -220
- 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
|
-
});
|