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,425 +0,0 @@
1
- /**
2
- * Phase handler tests.
3
- *
4
- * Validates that each phase handler:
5
- * - Has the correct phase name
6
- * - Builds a system prompt that includes phase-specific content
7
- * - Returns references
8
- * - Provides extractResult and isComplete logic
9
- */
10
- import { describe, it, expect } from 'vitest';
11
- import { createPhaseHandler, getPhaseOrder, getNextPhase } from '../../../src/phases/phaseHandlers.js';
12
- import { backtrackSession } from '../../../src/sessions/sessionManager.js';
13
- function makeSession(overrides) {
14
- return {
15
- sessionId: 'test-session',
16
- schemaVersion: '1.0.0',
17
- createdAt: '2025-01-01T00:00:00Z',
18
- updatedAt: '2025-01-01T00:00:00Z',
19
- phase: 'Discover',
20
- status: 'Active',
21
- participants: [],
22
- artifacts: { generatedFiles: [] },
23
- ...overrides,
24
- };
25
- }
26
- describe('phaseHandlers', () => {
27
- describe('createPhaseHandler', () => {
28
- it('creates a Discover handler', async () => {
29
- const handler = createPhaseHandler('Discover');
30
- expect(handler.phase).toBe('Discover');
31
- await handler._preload();
32
- const prompt = handler.buildSystemPrompt(makeSession());
33
- expect(prompt).toContain('Discover');
34
- });
35
- it('creates an Ideate handler', async () => {
36
- const handler = createPhaseHandler('Ideate');
37
- expect(handler.phase).toBe('Ideate');
38
- await handler._preload();
39
- const prompt = handler.buildSystemPrompt(makeSession());
40
- expect(prompt.length).toBeGreaterThan(50);
41
- });
42
- it('creates a Design handler', async () => {
43
- const handler = createPhaseHandler('Design');
44
- expect(handler.phase).toBe('Design');
45
- await handler._preload();
46
- const prompt = handler.buildSystemPrompt(makeSession());
47
- expect(prompt.length).toBeGreaterThan(50);
48
- });
49
- it('creates a Select handler', async () => {
50
- const handler = createPhaseHandler('Select');
51
- expect(handler.phase).toBe('Select');
52
- await handler._preload();
53
- const prompt = handler.buildSystemPrompt(makeSession());
54
- expect(prompt.length).toBeGreaterThan(50);
55
- });
56
- it('creates a Plan handler', async () => {
57
- const handler = createPhaseHandler('Plan');
58
- expect(handler.phase).toBe('Plan');
59
- await handler._preload();
60
- const prompt = handler.buildSystemPrompt(makeSession());
61
- expect(prompt.length).toBeGreaterThan(50);
62
- });
63
- it('creates a Develop handler', async () => {
64
- const handler = createPhaseHandler('Develop');
65
- expect(handler.phase).toBe('Develop');
66
- await handler._preload();
67
- const prompt = handler.buildSystemPrompt(makeSession());
68
- expect(prompt.length).toBeGreaterThan(50);
69
- });
70
- });
71
- describe('Discover handler specifics', () => {
72
- it('includes grounding references after preload', async () => {
73
- const handler = createPhaseHandler('Discover');
74
- await handler._preload();
75
- const refs = handler.getReferences(makeSession());
76
- expect(refs.length).toBeGreaterThan(0);
77
- });
78
- it('isComplete returns false without business context', () => {
79
- const handler = createPhaseHandler('Discover');
80
- expect(handler.isComplete(makeSession(), '')).toBe(false);
81
- });
82
- it('isComplete returns true with business context and workflow', () => {
83
- const handler = createPhaseHandler('Discover');
84
- const session = makeSession({
85
- businessContext: {
86
- businessDescription: 'Widget Co',
87
- challenges: ['Growth'],
88
- },
89
- workflow: {
90
- activities: [{ id: 'a1', name: 'Activity 1' }],
91
- edges: [],
92
- },
93
- });
94
- expect(handler.isComplete(session, '')).toBe(true);
95
- });
96
- // T063 — session naming via extractResult
97
- it('extractResult sets session.name when sessionName is present in LLM response', () => {
98
- const handler = createPhaseHandler('Discover');
99
- const session = makeSession();
100
- const response = '```json\n{"businessDescription": "Logistics Co", "challenges": ["Routing"], "sessionName": "Logistics AI Routing"}\n```';
101
- const result = handler.extractResult(session, response);
102
- expect(result.name).toBe('Logistics AI Routing');
103
- });
104
- it('extractResult does not set session.name when sessionName is absent from LLM response', () => {
105
- const handler = createPhaseHandler('Discover');
106
- const session = makeSession();
107
- const response = '```json\n{"businessDescription": "Tech Co", "challenges": ["Scale"]}\n```';
108
- const result = handler.extractResult(session, response);
109
- expect(result.name).toBeUndefined();
110
- });
111
- it('extractResult does not overwrite existing session.name (first-write-wins)', () => {
112
- const handler = createPhaseHandler('Discover');
113
- const session = makeSession({ name: 'Original Name' });
114
- const response = '```json\n{"businessDescription": "Retail Co", "challenges": ["Growth"], "sessionName": "New Name"}\n```';
115
- const result = handler.extractResult(session, response);
116
- // Should NOT include name in updates since session already has one
117
- expect(result.name).toBeUndefined();
118
- });
119
- });
120
- describe('Ideate handler specifics', () => {
121
- it('includes business context in prompt when available', async () => {
122
- const handler = createPhaseHandler('Ideate');
123
- await handler._preload();
124
- const session = makeSession({
125
- businessContext: {
126
- businessDescription: 'ACME Corp sells rockets',
127
- challenges: ['Supply chain delays'],
128
- },
129
- });
130
- const prompt = handler.buildSystemPrompt(session);
131
- expect(prompt).toContain('ACME Corp sells rockets');
132
- expect(prompt).toContain('Supply chain delays');
133
- });
134
- it('isComplete returns true when ideas exist', () => {
135
- const handler = createPhaseHandler('Ideate');
136
- const session = makeSession({
137
- ideas: [
138
- {
139
- id: 'i1',
140
- title: 'Smart Inventory',
141
- description: 'AI-powered inventory management',
142
- workflowStepIds: ['a1'],
143
- },
144
- ],
145
- });
146
- expect(handler.isComplete(session, '')).toBe(true);
147
- });
148
- });
149
- describe('Select handler specifics', () => {
150
- it('isComplete returns true when selection is confirmed', () => {
151
- const handler = createPhaseHandler('Select');
152
- const session = makeSession({
153
- selection: {
154
- ideaId: 'i1',
155
- selectionRationale: 'Best fit',
156
- confirmedByUser: true,
157
- confirmedAt: '2025-01-01T00:00:00Z',
158
- },
159
- });
160
- expect(handler.isComplete(session, '')).toBe(true);
161
- });
162
- it('isComplete returns false when selection not confirmed', () => {
163
- const handler = createPhaseHandler('Select');
164
- const session = makeSession({
165
- selection: {
166
- ideaId: 'i1',
167
- selectionRationale: 'Best fit',
168
- confirmedByUser: false,
169
- },
170
- });
171
- expect(handler.isComplete(session, '')).toBe(false);
172
- });
173
- });
174
- describe('getPhaseOrder', () => {
175
- it('returns 6 phases in order', () => {
176
- const order = getPhaseOrder();
177
- expect(order).toEqual(['Discover', 'Ideate', 'Design', 'Select', 'Plan', 'Develop']);
178
- });
179
- });
180
- describe('getNextPhase', () => {
181
- it('returns Ideate after Discover', () => {
182
- expect(getNextPhase('Discover')).toBe('Ideate');
183
- });
184
- it('returns null after Develop', () => {
185
- expect(getNextPhase('Develop')).toBeNull();
186
- });
187
- it('returns null for Complete', () => {
188
- expect(getNextPhase('Complete')).toBeNull();
189
- });
190
- });
191
- describe('extractResult integration', () => {
192
- it('Discover handler extracts business context from response', () => {
193
- const handler = createPhaseHandler('Discover');
194
- const session = makeSession();
195
- const response = '```json\n{"businessDescription": "Tech Co", "challenges": ["Scale"]}\n```';
196
- const result = handler.extractResult(session, response);
197
- expect(result.businessContext).toBeDefined();
198
- expect(result.businessContext.businessDescription).toBe('Tech Co');
199
- });
200
- it('Ideate handler extracts ideas from response', () => {
201
- const handler = createPhaseHandler('Ideate');
202
- const session = makeSession();
203
- const response = '```json\n[{"id": "i1", "title": "AI Chat", "description": "Chatbot", "workflowStepIds": ["s1"]}]\n```';
204
- const result = handler.extractResult(session, response);
205
- expect(result.ideas).toHaveLength(1);
206
- expect(result.ideas[0].title).toBe('AI Chat');
207
- });
208
- it('Ideate handler merges ideas without duplicates', () => {
209
- const handler = createPhaseHandler('Ideate');
210
- const session = makeSession({
211
- ideas: [{ id: 'i1', title: 'Existing', description: 'Already there', workflowStepIds: ['s1'] }],
212
- });
213
- const response = '```json\n[{"id": "i1", "title": "Dup", "description": "Dup", "workflowStepIds": ["s1"]}, {"id": "i2", "title": "New", "description": "New one", "workflowStepIds": ["s2"]}]\n```';
214
- const result = handler.extractResult(session, response);
215
- expect(result.ideas).toHaveLength(2);
216
- expect(result.ideas[0].title).toBe('Existing');
217
- expect(result.ideas[1].title).toBe('New');
218
- });
219
- it('Design handler extracts evaluation from response', () => {
220
- const handler = createPhaseHandler('Design');
221
- const session = makeSession();
222
- const response = '```json\n{"method": "feasibility-value-matrix", "ideas": [{"ideaId": "i1", "feasibility": 4, "value": 5}]}\n```';
223
- const result = handler.extractResult(session, response);
224
- expect(result.evaluation).toBeDefined();
225
- expect(result.evaluation.method).toBe('feasibility-value-matrix');
226
- });
227
- it('Select handler extracts selection from response', () => {
228
- const handler = createPhaseHandler('Select');
229
- const session = makeSession();
230
- const response = '```json\n{"ideaId": "i1", "selectionRationale": "Best ROI", "confirmedByUser": true}\n```';
231
- const result = handler.extractResult(session, response);
232
- expect(result.selection).toBeDefined();
233
- expect(result.selection.ideaId).toBe('i1');
234
- });
235
- it('Plan handler extracts plan from response', () => {
236
- const handler = createPhaseHandler('Plan');
237
- const session = makeSession();
238
- const response = '```json\n{"milestones": [{"id": "m1", "title": "Setup", "items": ["Init repo"]}]}\n```';
239
- const result = handler.extractResult(session, response);
240
- expect(result.plan).toBeDefined();
241
- expect(result.plan.milestones).toHaveLength(1);
242
- });
243
- it('Develop handler extracts poc state from response', () => {
244
- const handler = createPhaseHandler('Develop');
245
- const session = makeSession();
246
- const response = '```json\n{"repoSource": "local", "iterations": [{"iteration": 1, "startedAt": "2025-01-01T00:00:00Z", "outcome": "scaffold", "filesChanged": []}]}\n```';
247
- const result = handler.extractResult(session, response);
248
- expect(result.poc).toBeDefined();
249
- expect(result.poc.iterations).toHaveLength(1);
250
- });
251
- it('returns empty object when response has no JSON', () => {
252
- const handler = createPhaseHandler('Discover');
253
- const result = handler.extractResult(makeSession(), 'Just plain text');
254
- expect(result).toEqual({});
255
- });
256
- });
257
- describe('backtrack + recompute (T040)', () => {
258
- it('handlers detect cleared state as incomplete after backtrack', () => {
259
- // Session that completed Discover and Ideate
260
- const session = makeSession({
261
- phase: 'Design',
262
- businessContext: {
263
- businessDescription: 'ACME',
264
- challenges: ['Growth'],
265
- },
266
- workflow: {
267
- activities: [{ id: 'a1', name: 'Step 1' }],
268
- edges: [],
269
- },
270
- ideas: [
271
- { id: 'i1', title: 'Idea A', description: 'Desc', workflowStepIds: ['a1'] },
272
- ],
273
- });
274
- // Backtrack to Ideate: clears ideas (and anything downstream)
275
- const result = backtrackSession(session, 'Ideate');
276
- expect(result.success).toBe(true);
277
- const backtracked = result.session;
278
- const ideateHandler = createPhaseHandler('Ideate');
279
- // Ideas were cleared, so handler says incomplete
280
- expect(ideateHandler.isComplete(backtracked, '')).toBe(false);
281
- // Discover data is preserved
282
- expect(backtracked.businessContext).toBeDefined();
283
- });
284
- it('handlers recompute fresh data after backtrack', () => {
285
- const session = makeSession({
286
- phase: 'Design',
287
- businessContext: {
288
- businessDescription: 'ACME',
289
- challenges: ['Growth'],
290
- },
291
- ideas: [
292
- { id: 'old', title: 'Old Idea', description: 'Was here', workflowStepIds: ['a1'] },
293
- ],
294
- evaluation: {
295
- method: 'feasibility-value-matrix',
296
- ideas: [{ ideaId: 'old', feasibility: 3, value: 3 }],
297
- },
298
- });
299
- // Backtrack to Ideate clears ideas + evaluation
300
- const { session: backtracked } = backtrackSession(session, 'Ideate');
301
- expect(backtracked.ideas).toBeUndefined();
302
- expect(backtracked.evaluation).toBeUndefined();
303
- // Re-running Ideate handler extracts fresh ideas
304
- const handler = createPhaseHandler('Ideate');
305
- const freshResponse = '```json\n[{"id": "new1", "title": "New Idea", "description": "Fresh", "workflowStepIds": ["a1"]}]\n```';
306
- const result = handler.extractResult(backtracked, freshResponse);
307
- expect(result.ideas).toHaveLength(1);
308
- expect(result.ideas[0].id).toBe('new1');
309
- });
310
- });
311
- // ── T074: getInitialMessage() ──────────────────────────────────────────
312
- describe('getInitialMessage (T074)', () => {
313
- const phases = ['Discover', 'Ideate', 'Design', 'Select', 'Plan', 'Develop'];
314
- it('generates phase introduction for new sessions (no turns)', () => {
315
- for (const phase of phases) {
316
- const handler = createPhaseHandler(phase);
317
- const session = makeSession({ phase, turns: [] });
318
- const msg = handler.getInitialMessage(session);
319
- expect(msg).toBeDefined();
320
- expect(typeof msg).toBe('string');
321
- expect(msg.length).toBeGreaterThan(0);
322
- }
323
- });
324
- it('generates progress summary for resumed sessions (existing turns)', () => {
325
- for (const phase of phases) {
326
- const handler = createPhaseHandler(phase);
327
- const session = makeSession({
328
- phase,
329
- turns: [
330
- { phase, sequence: 1, role: 'user', content: 'Previous input', timestamp: '2025-01-01T00:00:00Z' },
331
- { phase, sequence: 2, role: 'assistant', content: 'Previous response', timestamp: '2025-01-01T00:00:00Z' },
332
- ],
333
- });
334
- const msg = handler.getInitialMessage(session);
335
- expect(msg).toBeDefined();
336
- expect(typeof msg).toBe('string');
337
- expect(msg.length).toBeGreaterThan(0);
338
- }
339
- });
340
- it('returns different messages for new vs resumed sessions', () => {
341
- const handler = createPhaseHandler('Discover');
342
- const newSession = makeSession({ phase: 'Discover', turns: [] });
343
- const resumedSession = makeSession({
344
- phase: 'Discover',
345
- turns: [
346
- { phase: 'Discover', sequence: 1, role: 'user', content: 'hello', timestamp: '2025-01-01T00:00:00Z' },
347
- { phase: 'Discover', sequence: 2, role: 'assistant', content: 'hi', timestamp: '2025-01-01T00:00:00Z' },
348
- ],
349
- });
350
- const newMsg = handler.getInitialMessage(newSession);
351
- const resumedMsg = handler.getInitialMessage(resumedSession);
352
- expect(newMsg).not.toBe(resumedMsg);
353
- });
354
- it('getInitialMessage exists on all 6 phase handlers', () => {
355
- for (const phase of phases) {
356
- const handler = createPhaseHandler(phase);
357
- expect(typeof handler.getInitialMessage).toBe('function');
358
- }
359
- });
360
- });
361
- // T040: Verify Ideate handler uses renderSummarizedContext (not ad-hoc context)
362
- describe('context summarizer integration', () => {
363
- it('Ideate handler uses unified summarized context', async () => {
364
- const handler = createPhaseHandler('Ideate');
365
- await handler._preload();
366
- const session = makeSession({
367
- businessContext: {
368
- businessDescription: 'Test Company',
369
- challenges: ['Challenge A'],
370
- },
371
- });
372
- const prompt = handler.buildSystemPrompt(session);
373
- expect(prompt).toContain('Test Company');
374
- expect(prompt).toContain('Prior Phase Context');
375
- });
376
- it('Design handler uses unified summarized context with ideas', async () => {
377
- const handler = createPhaseHandler('Design');
378
- await handler._preload();
379
- const session = makeSession({
380
- ideas: [
381
- { id: 'idea-1', title: 'AI Bot', description: 'Smart assistant', workflowStepIds: [] },
382
- ],
383
- });
384
- const prompt = handler.buildSystemPrompt(session);
385
- expect(prompt).toContain('AI Bot');
386
- expect(prompt).toContain('Prior Phase Context');
387
- });
388
- it('Select handler uses unified summarized context with evaluation', async () => {
389
- const handler = createPhaseHandler('Select');
390
- await handler._preload();
391
- const session = makeSession({
392
- evaluation: {
393
- method: 'feasibility-value-matrix',
394
- ideas: [{ ideaId: 'idea-1', feasibility: 8, value: 9 }],
395
- },
396
- });
397
- const prompt = handler.buildSystemPrompt(session);
398
- expect(prompt).toContain('feasibility-value-matrix');
399
- expect(prompt).toContain('Prior Phase Context');
400
- });
401
- it('Plan handler uses unified summarized context with selection', async () => {
402
- const handler = createPhaseHandler('Plan');
403
- await handler._preload();
404
- const session = makeSession({
405
- selection: {
406
- ideaId: 'idea-1',
407
- selectionRationale: 'Best overall',
408
- confirmedByUser: true,
409
- },
410
- });
411
- const prompt = handler.buildSystemPrompt(session);
412
- expect(prompt).toContain('idea-1');
413
- expect(prompt).toContain('Prior Phase Context');
414
- });
415
- it('handlers omit Prior Phase Context section when session is empty', async () => {
416
- for (const phase of ['Ideate', 'Design', 'Select', 'Plan', 'Develop']) {
417
- const handler = createPhaseHandler(phase);
418
- await handler._preload();
419
- const prompt = handler.buildSystemPrompt(makeSession());
420
- // Should not contain Prior Phase Context if session is empty
421
- expect(prompt).not.toContain('Prior Phase Context');
422
- }
423
- });
424
- });
425
- });
@@ -1,118 +0,0 @@
1
- /**
2
- * Prompt loader tests.
3
- *
4
- * Validates that canonical prompts can be loaded, composed, and
5
- * that reference documents are correctly mapped to phases.
6
- */
7
- import { describe, it, expect, beforeEach } from 'vitest';
8
- import { buildSystemPrompt, getPhaseReferences, loadReference, clearPromptCache, listPrompts, listReferences, } from '../../../src/prompts/promptLoader.js';
9
- describe('promptLoader', () => {
10
- beforeEach(() => {
11
- clearPromptCache();
12
- });
13
- describe('buildSystemPrompt', () => {
14
- it('loads the base system prompt for Discover phase', async () => {
15
- const prompt = await buildSystemPrompt('Discover');
16
- expect(prompt).toContain('sofIA');
17
- expect(prompt).toContain('AI Discovery Workshop');
18
- });
19
- it('includes phase-specific content for Discover', async () => {
20
- const prompt = await buildSystemPrompt('Discover');
21
- expect(prompt).toContain('Understand the Business');
22
- expect(prompt).toContain('Choose a Topic');
23
- expect(prompt).toContain('Map Workflow');
24
- });
25
- it('includes phase-specific content for Ideate', async () => {
26
- const prompt = await buildSystemPrompt('Ideate');
27
- expect(prompt).toContain('AI Envisioning Cards');
28
- expect(prompt).toContain('Score Cards');
29
- expect(prompt).toContain('Generate Ideas');
30
- });
31
- it('includes phase-specific content for Design', async () => {
32
- const prompt = await buildSystemPrompt('Design');
33
- expect(prompt).toContain('Evaluate Ideas');
34
- expect(prompt).toContain('Feasibility');
35
- expect(prompt).toContain('BXT');
36
- });
37
- it('includes phase-specific content for Select', async () => {
38
- const prompt = await buildSystemPrompt('Select');
39
- expect(prompt).toContain('Rank');
40
- expect(prompt).toContain('Recommend');
41
- expect(prompt).toContain('User Confirmation');
42
- });
43
- it('includes phase-specific content for Plan', async () => {
44
- const prompt = await buildSystemPrompt('Plan');
45
- expect(prompt).toContain('milestone');
46
- expect(prompt).toContain('Architecture');
47
- expect(prompt).toContain('PoC');
48
- });
49
- it('includes phase-specific content for Develop', async () => {
50
- const prompt = await buildSystemPrompt('Develop');
51
- expect(prompt).toContain('PoC Requirements');
52
- expect(prompt).toContain('Success Criteria');
53
- });
54
- it('returns only system prompt for Complete phase', async () => {
55
- const prompt = await buildSystemPrompt('Complete');
56
- // Complete phase gets only the base system prompt
57
- expect(prompt).toContain('sofIA');
58
- expect(prompt).not.toContain('Understand the Business');
59
- });
60
- it('caches prompts across calls', async () => {
61
- const prompt1 = await buildSystemPrompt('Discover');
62
- const prompt2 = await buildSystemPrompt('Discover');
63
- expect(prompt1).toBe(prompt2);
64
- });
65
- });
66
- describe('getPhaseReferences', () => {
67
- it('returns reference paths for Discover phase', async () => {
68
- const refs = await getPhaseReferences('Discover');
69
- expect(refs.length).toBeGreaterThan(0);
70
- expect(refs.some((r) => r.includes('facilitator_persona'))).toBe(true);
71
- expect(refs.some((r) => r.includes('guardrails'))).toBe(true);
72
- });
73
- it('includes design thinking docs for Ideate phase', async () => {
74
- const refs = await getPhaseReferences('Ideate');
75
- expect(refs.some((r) => r.includes('design_thinking_persona'))).toBe(true);
76
- expect(refs.some((r) => r.includes('design_thinking.md'))).toBe(true);
77
- });
78
- it('includes document generator for Complete phase', async () => {
79
- const refs = await getPhaseReferences('Complete');
80
- expect(refs.some((r) => r.includes('document_generator'))).toBe(true);
81
- });
82
- });
83
- describe('loadReference', () => {
84
- it('loads facilitator persona reference', async () => {
85
- const content = await loadReference('facilitatorPersona');
86
- expect(content).toContain('AI Workshop Facilitator');
87
- });
88
- it('loads design thinking reference', async () => {
89
- const content = await loadReference('designThinking');
90
- expect(content.length).toBeGreaterThan(100);
91
- });
92
- it('loads guardrails reference', async () => {
93
- const content = await loadReference('guardrails');
94
- expect(content).toContain('Guardrails');
95
- });
96
- });
97
- describe('listPrompts', () => {
98
- it('returns all prompt names', () => {
99
- const prompts = listPrompts();
100
- expect(prompts).toContain('system');
101
- expect(prompts).toContain('discover');
102
- expect(prompts).toContain('ideate');
103
- expect(prompts).toContain('design');
104
- expect(prompts).toContain('select');
105
- expect(prompts).toContain('plan');
106
- expect(prompts).toContain('develop');
107
- });
108
- });
109
- describe('listReferences', () => {
110
- it('returns all reference document keys', () => {
111
- const refs = listReferences();
112
- expect(refs).toContain('designThinking');
113
- expect(refs).toContain('facilitatorPersona');
114
- expect(refs).toContain('guardrails');
115
- expect(refs).toContain('documentGenerator');
116
- });
117
- });
118
- });