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.
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 +8 -27
  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 -328
  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,352 +0,0 @@
1
- /**
2
- * Phase extraction logic tests.
3
- *
4
- * Tests for extractResult() implementations that parse structured JSON
5
- * blocks from LLM responses and map them to WorkshopSession fields.
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import { extractJsonBlock, extractBusinessContext, extractWorkflow, extractIdeas, extractEvaluation, extractSelection, extractPlan, extractPocState, extractSessionName, extractAllJsonBlocks, extractJsonBlockForSchema, } from '../../../src/phases/phaseExtractors.js';
9
- import { z } from '../../../src/vendor/zod.js';
10
- function _emptySession() {
11
- return {
12
- sessionId: 'test-1',
13
- schemaVersion: '1.0.0',
14
- createdAt: '2025-01-01T00:00:00Z',
15
- updatedAt: '2025-01-01T00:00:00Z',
16
- phase: 'Discover',
17
- status: 'Active',
18
- participants: [],
19
- artifacts: { generatedFiles: [] },
20
- turns: [],
21
- };
22
- }
23
- describe('extractJsonBlock', () => {
24
- it('extracts JSON from markdown code fence', () => {
25
- const response = `Here are the results:\n\`\`\`json\n{"key": "value"}\n\`\`\`\nDone.`;
26
- const result = extractJsonBlock(response);
27
- expect(result).toEqual({ key: 'value' });
28
- });
29
- it('extracts JSON from unfenced block', () => {
30
- const response = `Result: {"businessDescription": "A company"}`;
31
- const result = extractJsonBlock(response);
32
- expect(result).toEqual({ businessDescription: 'A company' });
33
- });
34
- it('returns null when no JSON found', () => {
35
- const response = 'This is a plain text response with no JSON.';
36
- const result = extractJsonBlock(response);
37
- expect(result).toBeNull();
38
- });
39
- it('handles malformed JSON gracefully', () => {
40
- const response = '```json\n{broken: json}\n```';
41
- const result = extractJsonBlock(response);
42
- expect(result).toBeNull();
43
- });
44
- it('extracts JSON array from code fence', () => {
45
- const response = '```json\n[{"id": "1"}, {"id": "2"}]\n```';
46
- const result = extractJsonBlock(response);
47
- expect(result).toEqual([{ id: '1' }, { id: '2' }]);
48
- });
49
- });
50
- describe('extractBusinessContext', () => {
51
- it('extracts valid business context from response', () => {
52
- const response = `Here's what I gathered:
53
-
54
- \`\`\`json
55
- {
56
- "businessDescription": "A logistics company",
57
- "challenges": ["Slow routing", "High costs"]
58
- }
59
- \`\`\``;
60
- const result = extractBusinessContext(response);
61
- expect(result).not.toBeNull();
62
- expect(result.businessDescription).toBe('A logistics company');
63
- expect(result.challenges).toEqual(['Slow routing', 'High costs']);
64
- });
65
- it('returns null for non-matching JSON', () => {
66
- const response = '```json\n{"unrelated": "data"}\n```';
67
- const result = extractBusinessContext(response);
68
- expect(result).toBeNull();
69
- });
70
- it('returns null when no JSON present', () => {
71
- const result = extractBusinessContext('No structured data here.');
72
- expect(result).toBeNull();
73
- });
74
- it('includes optional fields when present', () => {
75
- const response = `\`\`\`json
76
- {
77
- "businessDescription": "SaaS platform",
78
- "challenges": ["Scale"],
79
- "constraints": ["Budget limited"],
80
- "successMetrics": [{"name": "Revenue", "value": "10M", "unit": "USD"}]
81
- }
82
- \`\`\``;
83
- const result = extractBusinessContext(response);
84
- expect(result.constraints).toEqual(['Budget limited']);
85
- expect(result.successMetrics).toHaveLength(1);
86
- });
87
- });
88
- describe('extractWorkflow', () => {
89
- it('extracts workflow map with activities and edges', () => {
90
- const response = `\`\`\`json
91
- {
92
- "activities": [
93
- {"id": "s1", "name": "Receive Order"},
94
- {"id": "s2", "name": "Process Order"}
95
- ],
96
- "edges": [
97
- {"fromStepId": "s1", "toStepId": "s2"}
98
- ]
99
- }
100
- \`\`\``;
101
- const result = extractWorkflow(response);
102
- expect(result).not.toBeNull();
103
- expect(result.activities).toHaveLength(2);
104
- expect(result.edges).toHaveLength(1);
105
- });
106
- it('returns null for invalid workflow', () => {
107
- const response = '```json\n{"name": "not a workflow"}\n```';
108
- const result = extractWorkflow(response);
109
- expect(result).toBeNull();
110
- });
111
- });
112
- describe('extractIdeas', () => {
113
- it('extracts array of idea cards', () => {
114
- const response = `\`\`\`json
115
- [
116
- {
117
- "id": "idea-1",
118
- "title": "AI Routing",
119
- "description": "Use AI to optimize delivery routes",
120
- "workflowStepIds": ["s1", "s2"]
121
- }
122
- ]
123
- \`\`\``;
124
- const result = extractIdeas(response);
125
- expect(result).toHaveLength(1);
126
- expect(result[0].title).toBe('AI Routing');
127
- });
128
- it('extracts ideas from object wrapper', () => {
129
- const response = `\`\`\`json
130
- {
131
- "ideas": [
132
- {
133
- "id": "idea-1",
134
- "title": "AI Routing",
135
- "description": "Optimize routes",
136
- "workflowStepIds": ["s1"]
137
- }
138
- ]
139
- }
140
- \`\`\``;
141
- const result = extractIdeas(response);
142
- expect(result).toHaveLength(1);
143
- });
144
- it('returns null when no ideas found', () => {
145
- const result = extractIdeas('No ideas in this response');
146
- expect(result).toBeNull();
147
- });
148
- });
149
- describe('extractEvaluation', () => {
150
- it('extracts evaluation with method', () => {
151
- const response = `\`\`\`json
152
- {
153
- "method": "feasibility-value-matrix",
154
- "ideas": [
155
- {
156
- "ideaId": "idea-1",
157
- "feasibility": 4,
158
- "value": 5,
159
- "risks": ["Data quality"]
160
- }
161
- ]
162
- }
163
- \`\`\``;
164
- const result = extractEvaluation(response);
165
- expect(result).not.toBeNull();
166
- expect(result.method).toBe('feasibility-value-matrix');
167
- expect(result.ideas).toHaveLength(1);
168
- });
169
- it('returns null for non-evaluation JSON', () => {
170
- const result = extractEvaluation('```json\n{"foo": "bar"}\n```');
171
- expect(result).toBeNull();
172
- });
173
- });
174
- describe('extractSelection', () => {
175
- it('extracts selected idea', () => {
176
- const response = `\`\`\`json
177
- {
178
- "ideaId": "idea-1",
179
- "selectionRationale": "Highest combined score",
180
- "confirmedByUser": false
181
- }
182
- \`\`\``;
183
- const result = extractSelection(response);
184
- expect(result).not.toBeNull();
185
- expect(result.ideaId).toBe('idea-1');
186
- expect(result.confirmedByUser).toBe(false);
187
- });
188
- });
189
- describe('extractPlan', () => {
190
- it('extracts implementation plan with milestones', () => {
191
- const response = `\`\`\`json
192
- {
193
- "milestones": [
194
- {
195
- "id": "m1",
196
- "title": "Setup",
197
- "items": ["Initialize project", "Configure CI"]
198
- },
199
- {
200
- "id": "m2",
201
- "title": "Core Features",
202
- "items": ["Implement routing", "Add monitoring"]
203
- }
204
- ],
205
- "architectureNotes": "Microservices architecture",
206
- "dependencies": ["Azure Maps API"]
207
- }
208
- \`\`\``;
209
- const result = extractPlan(response);
210
- expect(result).not.toBeNull();
211
- expect(result.milestones).toHaveLength(2);
212
- expect(result.architectureNotes).toBe('Microservices architecture');
213
- });
214
- });
215
- describe('extractPocState', () => {
216
- it('extracts PoC development state', () => {
217
- const response = `\`\`\`json
218
- {
219
- "repoSource": "local",
220
- "iterations": [
221
- {
222
- "iteration": 1,
223
- "startedAt": "2025-01-01T00:00:00Z",
224
- "outcome": "scaffold",
225
- "filesChanged": ["package.json", "src/index.ts"],
226
- "changesSummary": "Initial scaffold"
227
- }
228
- ]
229
- }
230
- \`\`\``;
231
- const result = extractPocState(response);
232
- expect(result).not.toBeNull();
233
- expect(result.iterations).toHaveLength(1);
234
- });
235
- it('returns null when no PoC data', () => {
236
- const result = extractPocState('Just text here');
237
- expect(result).toBeNull();
238
- });
239
- });
240
- // ── T062: extractSessionName ─────────────────────────────────────────────────
241
- describe('extractSessionName', () => {
242
- it('extracts sessionName from JSON block in response', () => {
243
- const response = `Here's the summary:
244
-
245
- \`\`\`json
246
- {
247
- "businessDescription": "A logistics company",
248
- "challenges": ["Slow routing"],
249
- "sessionName": "Logistics AI Routing"
250
- }
251
- \`\`\``;
252
- const result = extractSessionName(response);
253
- expect(result).toBe('Logistics AI Routing');
254
- });
255
- it('returns null when sessionName is missing from JSON block', () => {
256
- const response = `\`\`\`json
257
- {
258
- "businessDescription": "A company",
259
- "challenges": ["Growth"]
260
- }
261
- \`\`\``;
262
- const result = extractSessionName(response);
263
- expect(result).toBeNull();
264
- });
265
- it('returns null when no JSON block is present', () => {
266
- const result = extractSessionName('Just a plain text response with no JSON.');
267
- expect(result).toBeNull();
268
- });
269
- it('returns null when sessionName is not a string', () => {
270
- const response = `\`\`\`json
271
- { "sessionName": 42 }
272
- \`\`\``;
273
- const result = extractSessionName(response);
274
- expect(result).toBeNull();
275
- });
276
- it('trims whitespace from sessionName', () => {
277
- const response = `\`\`\`json
278
- { "sessionName": " Retail AI Onboarding " }
279
- \`\`\``;
280
- const result = extractSessionName(response);
281
- expect(result).toBe('Retail AI Onboarding');
282
- });
283
- it('returns null for empty string sessionName', () => {
284
- const response = `\`\`\`json
285
- { "sessionName": "" }
286
- \`\`\``;
287
- const result = extractSessionName(response);
288
- expect(result).toBeNull();
289
- });
290
- });
291
- // ── Multi-block extraction tests (T006, T007) ──────────────────────────────
292
- describe('extractAllJsonBlocks', () => {
293
- it('returns empty array when no JSON blocks found', () => {
294
- const response = 'This is a plain text response with no JSON.';
295
- expect(extractAllJsonBlocks(response)).toEqual([]);
296
- });
297
- it('extracts a single fenced JSON block', () => {
298
- const response = `Here are the results:\n\`\`\`json\n{"key": "value"}\n\`\`\`\nDone.`;
299
- const results = extractAllJsonBlocks(response);
300
- expect(results).toEqual([{ key: 'value' }]);
301
- });
302
- it('extracts two fenced JSON blocks', () => {
303
- const response = `First:\n\`\`\`json\n{"a": 1}\n\`\`\`\nSecond:\n\`\`\`json\n{"b": 2}\n\`\`\``;
304
- const results = extractAllJsonBlocks(response);
305
- expect(results).toHaveLength(2);
306
- expect(results[0]).toEqual({ a: 1 });
307
- expect(results[1]).toEqual({ b: 2 });
308
- });
309
- it('extracts three fenced JSON blocks', () => {
310
- const response = `\`\`\`json\n[1]\n\`\`\`\n\`\`\`json\n[2]\n\`\`\`\n\`\`\`json\n[3]\n\`\`\``;
311
- const results = extractAllJsonBlocks(response);
312
- expect(results).toHaveLength(3);
313
- });
314
- it('falls back to bracket-depth counting when no fenced blocks', () => {
315
- const response = 'Result: {"key": "value"} and also [1, 2, 3]';
316
- const results = extractAllJsonBlocks(response);
317
- expect(results).toHaveLength(2);
318
- expect(results[0]).toEqual({ key: 'value' });
319
- expect(results[1]).toEqual([1, 2, 3]);
320
- });
321
- it('skips invalid JSON in fenced blocks', () => {
322
- const response = `\`\`\`json\n{invalid}\n\`\`\`\n\`\`\`json\n{"valid": true}\n\`\`\``;
323
- const results = extractAllJsonBlocks(response);
324
- expect(results).toEqual([{ valid: true }]);
325
- });
326
- });
327
- describe('extractJsonBlockForSchema', () => {
328
- const simpleSchema = z.object({
329
- name: z.string(),
330
- age: z.number(),
331
- });
332
- it('returns null when no blocks match schema', () => {
333
- const response = `\`\`\`json\n{"unrelated": true}\n\`\`\``;
334
- expect(extractJsonBlockForSchema(response, simpleSchema)).toBeNull();
335
- });
336
- it('returns the first matching block when multiple blocks present', () => {
337
- const response = `\`\`\`json\n{"unrelated": true}\n\`\`\`\n\`\`\`json\n{"name": "Alice", "age": 30}\n\`\`\``;
338
- const result = extractJsonBlockForSchema(response, simpleSchema);
339
- expect(result).toEqual({ name: 'Alice', age: 30 });
340
- });
341
- it('skips first block that does not match and returns second that does', () => {
342
- const response = `\`\`\`json\n{"name": 123}\n\`\`\`\n\`\`\`json\n{"name": "Bob", "age": 25}\n\`\`\``;
343
- const result = extractJsonBlockForSchema(response, simpleSchema);
344
- expect(result).toEqual({ name: 'Bob', age: 25 });
345
- });
346
- it('works with array schemas', () => {
347
- const arraySchema = z.array(z.object({ id: z.string() }));
348
- const response = `\`\`\`json\n{"not": "array"}\n\`\`\`\n\`\`\`json\n[{"id": "a"}]\n\`\`\``;
349
- const result = extractJsonBlockForSchema(response, arraySchema);
350
- expect(result).toEqual([{ id: 'a' }]);
351
- });
352
- });